InPanel 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. inpanel/__init__.py +29 -0
  2. inpanel/acme.py +289 -0
  3. inpanel/aliyuncs.py +269 -0
  4. inpanel/app.py +514 -0
  5. inpanel/apt.py +223 -0
  6. inpanel/base.py +200 -0
  7. inpanel/config.py +139 -0
  8. inpanel/data/config.ini +29 -0
  9. inpanel/data/history +5 -0
  10. inpanel/data/runlogs.ini +12 -0
  11. inpanel/data/update_info.ini +4 -0
  12. inpanel/dnf.py +225 -0
  13. inpanel/lib/__init__.py +9 -0
  14. inpanel/lib/async_process.py +98 -0
  15. inpanel/lib/filelock.py +82 -0
  16. inpanel/lib/magic.py +233 -0
  17. inpanel/lib/pxssh.py +310 -0
  18. inpanel/lib/pyDes.py +867 -0
  19. inpanel/mod/__init__.py +0 -0
  20. inpanel/mod/apt.py +316 -0
  21. inpanel/mod/certificate.py +268 -0
  22. inpanel/mod/config.py +372 -0
  23. inpanel/mod/cron.py +315 -0
  24. inpanel/mod/disk.py +322 -0
  25. inpanel/mod/dnf.py +239 -0
  26. inpanel/mod/file.py +722 -0
  27. inpanel/mod/firewall.py +199 -0
  28. inpanel/mod/firewall_base.py +137 -0
  29. inpanel/mod/firewall_firewalld.py +170 -0
  30. inpanel/mod/firewall_iptables.py +176 -0
  31. inpanel/mod/firewall_macos.py +213 -0
  32. inpanel/mod/firewall_ufw.py +128 -0
  33. inpanel/mod/ftp.py +231 -0
  34. inpanel/mod/httpd.py +1205 -0
  35. inpanel/mod/lighttpd.py +26 -0
  36. inpanel/mod/login.py +98 -0
  37. inpanel/mod/mysql.py +577 -0
  38. inpanel/mod/named.py +26 -0
  39. inpanel/mod/nginx.py +2327 -0
  40. inpanel/mod/package.py +33 -0
  41. inpanel/mod/package_apt.py +62 -0
  42. inpanel/mod/package_base.py +77 -0
  43. inpanel/mod/package_dnf.py +62 -0
  44. inpanel/mod/package_map.py +366 -0
  45. inpanel/mod/package_yum.py +64 -0
  46. inpanel/mod/php.py +289 -0
  47. inpanel/mod/process.py +291 -0
  48. inpanel/mod/proftpd.py +26 -0
  49. inpanel/mod/pureftpd.py +26 -0
  50. inpanel/mod/query.py +108 -0
  51. inpanel/mod/server.py +1319 -0
  52. inpanel/mod/service.py +262 -0
  53. inpanel/mod/setting.py +228 -0
  54. inpanel/mod/shell.py +78 -0
  55. inpanel/mod/ssh.py +280 -0
  56. inpanel/mod/system.py +220 -0
  57. inpanel/mod/task.py +1337 -0
  58. inpanel/mod/ufw.py +14 -0
  59. inpanel/mod/user.py +306 -0
  60. inpanel/mod/vsftpd.py +67 -0
  61. inpanel/mod/yum.py +243 -0
  62. inpanel/plugins/__init__.py +10 -0
  63. inpanel/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
  64. inpanel/plugins/acme/static/index.html +10 -0
  65. inpanel/plugins/acme/static/index.js +13 -0
  66. inpanel/plugins/shadowsocks-libev/__pycache__/info.cpython-313.pyc +0 -0
  67. inpanel/plugins/shadowsocks-libev/info.py +73 -0
  68. inpanel/plugins/shadowsocks-libev/static/index.html +70 -0
  69. inpanel/plugins/shadowsocks-libev/static/index.js +18 -0
  70. inpanel/public/css/global.css +138 -0
  71. inpanel/public/favicon.ico +0 -0
  72. inpanel/public/images/alipay.jpg +0 -0
  73. inpanel/public/images/loading.gif +0 -0
  74. inpanel/public/images/weixin.jpg +0 -0
  75. inpanel/public/index.html +94 -0
  76. inpanel/public/js/controllers/controllers.js +566 -0
  77. inpanel/public/js/controllers/database.js +590 -0
  78. inpanel/public/js/controllers/ecs.js +624 -0
  79. inpanel/public/js/controllers/file.js +940 -0
  80. inpanel/public/js/controllers/plugins.js +105 -0
  81. inpanel/public/js/controllers/service.js +1460 -0
  82. inpanel/public/js/controllers/site.js +1241 -0
  83. inpanel/public/js/controllers/utils.js +2319 -0
  84. inpanel/public/js/core.js +221 -0
  85. inpanel/public/js/directives.js +784 -0
  86. inpanel/public/js/filters.js +395 -0
  87. inpanel/public/js/services.js +257 -0
  88. inpanel/public/lib/angular/1.0.2/angular-resource.min.js +10 -0
  89. inpanel/public/lib/angular/1.0.2/angular.min.js +158 -0
  90. inpanel/public/lib/bootstrap/css/bootstrap-theme.css +587 -0
  91. inpanel/public/lib/bootstrap/css/bootstrap-theme.css.map +1 -0
  92. inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css +6 -0
  93. inpanel/public/lib/bootstrap/css/bootstrap-theme.min.css.map +1 -0
  94. inpanel/public/lib/bootstrap/css/bootstrap.css +6757 -0
  95. inpanel/public/lib/bootstrap/css/bootstrap.css.map +1 -0
  96. inpanel/public/lib/bootstrap/css/bootstrap.min.css +6 -0
  97. inpanel/public/lib/bootstrap/css/bootstrap.min.css.map +1 -0
  98. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  99. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  100. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  101. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  102. inpanel/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  103. inpanel/public/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
  104. inpanel/public/lib/bootstrap/img/glyphicons-halflings.png +0 -0
  105. inpanel/public/lib/bootstrap/js/bootstrap.js +2377 -0
  106. inpanel/public/lib/bootstrap/js/bootstrap.min.js +7 -0
  107. inpanel/public/lib/bootstrap/js/npm.js +13 -0
  108. inpanel/public/lib/codemirror/codemirror-compressed.js +1 -0
  109. inpanel/public/lib/codemirror/codemirror.css +173 -0
  110. inpanel/public/lib/jquery/jquery-1.12.4.min.js +5 -0
  111. inpanel/public/lib/md5/md5.js +379 -0
  112. inpanel/public/lib/md5/md5.min.js +1 -0
  113. inpanel/public/partials/database/index.html +172 -0
  114. inpanel/public/partials/database/mysql/dbedit.html +462 -0
  115. inpanel/public/partials/database/mysql/dbnew.html +348 -0
  116. inpanel/public/partials/database/mysql/useredit.html +310 -0
  117. inpanel/public/partials/database/mysql/usernew.html +97 -0
  118. inpanel/public/partials/directives/message.html +20 -0
  119. inpanel/public/partials/directives/navbar.html +25 -0
  120. inpanel/public/partials/directives/selector.html +57 -0
  121. inpanel/public/partials/directives/srvbase.html +72 -0
  122. inpanel/public/partials/directives/srvext.html +34 -0
  123. inpanel/public/partials/directives/srvfile.html +23 -0
  124. inpanel/public/partials/directives/srvinstall.html +34 -0
  125. inpanel/public/partials/directives/srvlog.html +23 -0
  126. inpanel/public/partials/directives/srvminiop.html +28 -0
  127. inpanel/public/partials/directives/srvuninstall.html +38 -0
  128. inpanel/public/partials/directives/srvupdate.html +30 -0
  129. inpanel/public/partials/ecs/account.html +156 -0
  130. inpanel/public/partials/ecs/ecs.html +198 -0
  131. inpanel/public/partials/ecs/index.html +40 -0
  132. inpanel/public/partials/ecs/setings.html +459 -0
  133. inpanel/public/partials/file/file.html +501 -0
  134. inpanel/public/partials/file/trash.html +95 -0
  135. inpanel/public/partials/ftp.html +36 -0
  136. inpanel/public/partials/log.html +83 -0
  137. inpanel/public/partials/login.html +79 -0
  138. inpanel/public/partials/logout.html +22 -0
  139. inpanel/public/partials/main.html +534 -0
  140. inpanel/public/partials/plugins/index.html +55 -0
  141. inpanel/public/partials/plugins/plugins.html +13 -0
  142. inpanel/public/partials/secure.html +13 -0
  143. inpanel/public/partials/service/apache.html +147 -0
  144. inpanel/public/partials/service/cron.html +71 -0
  145. inpanel/public/partials/service/index.html +293 -0
  146. inpanel/public/partials/service/iptables.html +40 -0
  147. inpanel/public/partials/service/lighttpd.html +63 -0
  148. inpanel/public/partials/service/mariadb.html +113 -0
  149. inpanel/public/partials/service/memcache.html +36 -0
  150. inpanel/public/partials/service/minio.html +44 -0
  151. inpanel/public/partials/service/mongodb.html +44 -0
  152. inpanel/public/partials/service/mysql.html +113 -0
  153. inpanel/public/partials/service/named.html +61 -0
  154. inpanel/public/partials/service/nginx.html +296 -0
  155. inpanel/public/partials/service/ntp.html +40 -0
  156. inpanel/public/partials/service/php.html +250 -0
  157. inpanel/public/partials/service/proftpd.html +59 -0
  158. inpanel/public/partials/service/pureftpd.html +59 -0
  159. inpanel/public/partials/service/redis.html +40 -0
  160. inpanel/public/partials/service/samba.html +58 -0
  161. inpanel/public/partials/service/sendmail.html +40 -0
  162. inpanel/public/partials/service/ssh.html +186 -0
  163. inpanel/public/partials/service/tomcat.html +65 -0
  164. inpanel/public/partials/service/vsftpd.html +195 -0
  165. inpanel/public/partials/setting.html +299 -0
  166. inpanel/public/partials/site/apache/site.html +277 -0
  167. inpanel/public/partials/site/index.html +344 -0
  168. inpanel/public/partials/site/nginx/site.html +558 -0
  169. inpanel/public/partials/sorry.html +19 -0
  170. inpanel/public/partials/storage/autofm.html +103 -0
  171. inpanel/public/partials/storage/backup.html +42 -0
  172. inpanel/public/partials/storage/index.html +360 -0
  173. inpanel/public/partials/storage/migrate.html +93 -0
  174. inpanel/public/partials/storage/movedata.html +98 -0
  175. inpanel/public/partials/storage/remote.html +77 -0
  176. inpanel/public/partials/utils/cron.html +486 -0
  177. inpanel/public/partials/utils/firewall.html +266 -0
  178. inpanel/public/partials/utils/index.html +193 -0
  179. inpanel/public/partials/utils/network.html +118 -0
  180. inpanel/public/partials/utils/process.html +239 -0
  181. inpanel/public/partials/utils/repository.html +373 -0
  182. inpanel/public/partials/utils/shell.html +28 -0
  183. inpanel/public/partials/utils/ssl.html +319 -0
  184. inpanel/public/partials/utils/time.html +109 -0
  185. inpanel/public/partials/utils/user.html +359 -0
  186. inpanel/public/robots.txt +2 -0
  187. inpanel/remote.py +109 -0
  188. inpanel/templates/file/preview.html +34 -0
  189. inpanel/templates/index.html +93 -0
  190. inpanel/utils.py +282 -0
  191. inpanel/web.py +1811 -0
  192. inpanel/yum.py +756 -0
  193. inpanel-1.2.1.dist-info/METADATA +399 -0
  194. inpanel-1.2.1.dist-info/RECORD +198 -0
  195. inpanel-1.2.1.dist-info/WHEEL +5 -0
  196. inpanel-1.2.1.dist-info/entry_points.txt +2 -0
  197. inpanel-1.2.1.dist-info/licenses/LICENSE +30 -0
  198. inpanel-1.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (c) 2017-2026 Jackson Dou
4
+ # All rights reserved.
5
+ #
6
+ # InPanel is distributed under the terms of the New BSD License.
7
+ # The full license can be found in 'LICENSE'.
8
+
9
+ """Module for Lighttpd configuration management."""
10
+
11
+
12
+ def web_handler(self):
13
+ action = self.get_argument('action', '')
14
+ if action == 'getsettings':
15
+ self.write({'code': 0, 'msg': 'Lighttpd 配置信息获取成功!', 'data': get_config()})
16
+ elif action == 'savesettings':
17
+ self.write({'code': 0, 'msg': 'Lighttpd 服务配置保存成功!', 'data': set_config(self)})
18
+ return
19
+
20
+
21
+ def get_config():
22
+ return dict()
23
+
24
+
25
+ def set_config(self):
26
+ return dict()
inpanel/mod/login.py ADDED
@@ -0,0 +1,98 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (c) 2017-2026 Jackson Dou
4
+ # All rights reserved.
5
+ #
6
+ # InPanel is distributed under the terms of the (new) BSD License.
7
+ # The full license can be found in 'LICENSE'.
8
+
9
+ '''Module for login handling'''
10
+
11
+ import time
12
+ from datetime import datetime
13
+ from hashlib import md5
14
+ import hmac
15
+
16
+
17
+ def handle_login(config, username, password):
18
+ """Handle login request.
19
+
20
+ Args:
21
+ config: config object
22
+ username: username from request
23
+ password: password from request
24
+
25
+ Returns:
26
+ dict with 'code' and 'msg' keys
27
+ """
28
+ loginlock = config.get('runtime', 'loginlock')
29
+
30
+ if config.get('runtime', 'mode') == 'demo':
31
+ loginlock = 'off'
32
+
33
+ # check if login is locked
34
+ if loginlock == 'on':
35
+ loginlockexpire = config.getint('runtime', 'loginlockexpire')
36
+ if time.time() < loginlockexpire:
37
+ return {
38
+ 'code': -1,
39
+ 'msg': '登录已被锁定,请在 %s 后重试登录。<br>'\
40
+ '如需立即解除锁定,请在服务器上执行以下命令:<br>'\
41
+ 'inpanel config loginlock off' %
42
+ datetime.fromtimestamp(loginlockexpire)
43
+ .strftime('%Y-%m-%d %H:%M:%S')
44
+ }
45
+ else:
46
+ config.set('runtime', 'loginlock', 'off')
47
+ config.set('runtime', 'loginlockexpire', 0)
48
+
49
+ loginfails = config.getint('runtime', 'loginfails')
50
+ cfg_username = config.get('auth', 'username')
51
+ cfg_password = config.get('auth', 'password')
52
+
53
+ if not username:
54
+ return {'code': -1, 'msg': '账号不能为空!'}
55
+ elif not password:
56
+ return {'code': -1, 'msg': '密码不能为空!'}
57
+ elif cfg_password == '':
58
+ return {
59
+ 'code': -1,
60
+ 'msg': '登录密码还未设置,请在服务器上执行以下命令进行设置:<br>inpanel config password \'您的密码\''
61
+ }
62
+ elif username != cfg_username:
63
+ return {'code': -1, 'msg': '用户不存在!'}
64
+ else:
65
+ cfg_password, key = cfg_password.split(':')
66
+ if hmac.new(key.encode('utf-8'), password.encode('utf-8'), md5).hexdigest() == cfg_password:
67
+ if loginfails > 0:
68
+ config.set('runtime', 'loginfails', 0)
69
+
70
+ passwordcheck = config.getboolean('auth', 'passwordcheck')
71
+ if passwordcheck:
72
+ return {'code': 1, 'msg': '%s,您已登录成功!' % username}
73
+ else:
74
+ return {'code': 0, 'msg': '%s,您已登录成功!' % username}
75
+ else:
76
+ if config.get('runtime', 'mode') == 'demo':
77
+ return {'code': -1, 'msg': '用户名或密码错误!'}
78
+
79
+ loginfails = loginfails + 1
80
+ config.set('runtime', 'loginfails', loginfails)
81
+ if loginfails >= 5:
82
+ # lock 24 hours
83
+ config.set('runtime', 'loginlock', 'on')
84
+ config.set('runtime', 'loginlockexpire', int(time.time()) + 86400)
85
+ return {'code': -1, 'msg': '用户名或密码错误!<br>'\
86
+ '已连续错误 5 次,登录已被禁止!'}
87
+ else:
88
+ return {'code': -1, 'msg': '用户名或密码错误!<br>'\
89
+ '连续错误 5 次后将被禁止登录,还有 %d 次机会。' % (5 - loginfails)}
90
+
91
+
92
+ def handle_logout():
93
+ """Handle logout request.
94
+
95
+ Returns:
96
+ None (logout is handled by clearing cookie)
97
+ """
98
+ pass
inpanel/mod/mysql.py ADDED
@@ -0,0 +1,577 @@
1
+ #!/usr/bin/env python3
2
+ #-*- coding: utf-8 -*-
3
+ #
4
+ # Copyright (c) 2017-2026 Jackson Dou
5
+ # All rights reserved.
6
+ #
7
+ # InPanel is distributed under the terms of the (new) BSD License.
8
+ # The full license can be found in 'LICENSE'.
9
+
10
+ """Module for mysql operations."""
11
+
12
+ import os
13
+ import re
14
+ import shlex
15
+ import time
16
+ from pathlib import Path
17
+
18
+ import pexpect
19
+ from ..utils import valid_filename
20
+
21
+
22
+ def updatepwd(pwd, oldpwd):
23
+ """Update password of root.
24
+ """
25
+ try:
26
+ cmd = shlex.split(f'mysqladmin -uroot password "{pwd}" -p')
27
+ except:
28
+ return False
29
+
30
+ child = pexpect.spawn(cmd[0], cmd[1:])
31
+ i = child.expect(['Enter password', pexpect.EOF])
32
+ if i == 1:
33
+ if child.isalive():
34
+ child.wait()
35
+ return False
36
+
37
+ child.sendline(oldpwd)
38
+ i = child.expect(['error', pexpect.EOF])
39
+ if child.isalive():
40
+ return child.wait() == 0
41
+ return i != 0
42
+
43
+ def shutdown(pwd):
44
+ """Shutdown mysql server.
45
+ """
46
+ try:
47
+ cmd = shlex.split('mysqladmin -uroot shutdown -p')
48
+ except:
49
+ return False
50
+
51
+ child = pexpect.spawn(cmd[0], cmd[1:])
52
+ i = child.expect(['Enter password', pexpect.EOF])
53
+ if i == 1:
54
+ if child.isalive(): child.wait()
55
+ return False
56
+
57
+ child.sendline(pwd)
58
+ i = child.expect(['error', pexpect.EOF])
59
+ if child.isalive(): return child.wait() == 0
60
+ return i != 0
61
+
62
+ def _mysql(pwd):
63
+ """Open a mysql client and auth login.
64
+ """
65
+ cmd = shlex.split('mysql -uroot -p -A')
66
+ child = pexpect.spawn(cmd[0], cmd[1:])
67
+ if not _auth(child, pwd): return False
68
+ return child
69
+
70
+ def _auth(child, pwd):
71
+ """Auth a mysql client login.
72
+ """
73
+ i = child.expect(['Enter password', pexpect.EOF])
74
+ if i == 1:
75
+ if child.isalive(): child.wait()
76
+ return False
77
+
78
+ child.sendline(pwd)
79
+ i = child.expect(['mysql>', pexpect.EOF])
80
+ if i == 1:
81
+ if child.isalive(): child.wait()
82
+ return False
83
+ return True
84
+
85
+ def _exit(child):
86
+ """Exit a mysql client.
87
+ """
88
+ child.sendline('exit')
89
+ child.expect([pexpect.EOF])
90
+ if child.isalive(): child.wait()
91
+
92
+ def _parse_result(output, includefields=True):
93
+ """Parse result into a list.
94
+ """
95
+ lines = output.split('\n')[1:]
96
+ if lines[0].startswith('Empty set'): return []
97
+ if lines[0].startswith('Query OK'): return True
98
+ if lines[0].startswith('+'):
99
+ if includefields:
100
+ fields = [f.strip() for f in lines[1].strip().strip('|').split('|')]
101
+ datalines = lines[3:]
102
+ rows = []
103
+ for dline in datalines:
104
+ if dline.startswith('|'):
105
+ rows.append([v.strip() for v in dline.strip().strip('|').split('|')])
106
+ else:
107
+ break
108
+ if includefields:
109
+ return [dict(zip(fields, row)) for row in rows]
110
+ else:
111
+ return rows
112
+ return True
113
+
114
+ def _sql(child, sql, returnresult=True, includefields=True):
115
+ """Execute SQL statement in interactive mode.
116
+ """
117
+ sql = sql.strip()
118
+ if '\n' in sql:
119
+ sql = ' '.join([line.strip() for line in sql.split('\n')])
120
+ if not sql.endswith(';'):
121
+ sql += ';'
122
+
123
+ child.sendline(sql)
124
+ i = child.expect(['ERROR', 'mysql>', pexpect.EOF])
125
+ if i == 0:
126
+ # skip old lines
127
+ i = child.expect(['mysql>', pexpect.EOF])
128
+ if i != 0: _exit(child)
129
+ return False
130
+ elif i == 2:
131
+ _exit(child)
132
+ return False
133
+
134
+ if returnresult:
135
+ return _parse_result(child.before, includefields=includefields)
136
+ else:
137
+ return True
138
+
139
+ def _escape(string):
140
+ """Escape a string.
141
+ """
142
+ return re.escape(string).replace(r'\_', '_').replace(r'\%', '%')
143
+
144
+ def fupdatepwd(pwd):
145
+ """Force update password of root.
146
+
147
+ MySQL server should first enter rescue mode.
148
+ """
149
+ child = pexpect.spawn('mysql -A')
150
+ i = child.expect(['mysql>', pexpect.EOF])
151
+ if i == 1:
152
+ if child.isalive(): child.wait()
153
+ return False
154
+
155
+ try:
156
+ if not _sql(child, 'UPDATE mysql.user SET Password=PASSWORD("%s") WHERE User="root"' % _escape(pwd)): raise Exception()
157
+ if not _sql(child, 'FLUSH PRIVILEGES'): raise Exception()
158
+ except:
159
+ _exit(child)
160
+ return False
161
+ else:
162
+ _exit(child)
163
+
164
+ return True
165
+
166
+ def checkpwd(pwd):
167
+ """Validate password of root.
168
+ """
169
+ child = _mysql(pwd)
170
+ if not child: return False
171
+ _exit(child)
172
+ return True
173
+
174
+ def show_databases(pwd, fullinfo=True):
175
+ """Show database list.
176
+ """
177
+ child = _mysql(pwd)
178
+ if not child: return False
179
+
180
+ ignore_tables = ('information_schema', 'performance_schema', 'mysql')
181
+
182
+ if not fullinfo:
183
+ dbs = _sql(child, 'SHOW DATABASES', includefields=False)
184
+ if not dbs:
185
+ _exit(child)
186
+ return False
187
+ dbs = [db[0] for db in dbs if db[0] not in ignore_tables]
188
+ else:
189
+ # REF: http://stackoverflow.com/questions/184560/how-to-monitor-mysql-space
190
+ sql = '''
191
+ SELECT schema_name name, charset, collation,
192
+ IFNULL(tables, 0) tables,
193
+ IFNULL(datsum, 0) datasize,
194
+ IFNULL(ndxsum, 0) indexsize,
195
+ IFNULL(totsum, 0) totalsize
196
+ FROM (
197
+ SELECT schema_name, default_character_set_name charset, default_collation_name collation
198
+ FROM information_schema.SCHEMATA
199
+ WHERE schema_name NOT IN %s
200
+ ) A LEFT JOIN (
201
+ SELECT db, COUNT(1) tables, SUM(dat) datsum, SUM(ndx) ndxsum, SUM(dat+ndx) totsum
202
+ FROM (
203
+ SELECT table_schema db, data_length dat, index_length ndx
204
+ FROM information_schema.tables WHERE engine IS NOT NULL
205
+ AND table_schema NOT IN %s
206
+ ) AA
207
+ GROUP BY db
208
+ ) B ON A.schema_name=B.db''' % (repr(ignore_tables), repr(ignore_tables))
209
+ dbs = _sql(child, sql)
210
+
211
+ _exit(child)
212
+ return dbs
213
+
214
+ def show_database(pwd, dbname):
215
+ """Show a database info.
216
+ """
217
+ child = _mysql(pwd)
218
+ if not child: return False
219
+
220
+ sql = '''
221
+ SELECT schema_name name, charset, collation,
222
+ IFNULL(tables, 0) tables,
223
+ IFNULL(datsum, 0) datasize,
224
+ IFNULL(ndxsum, 0) indexsize,
225
+ IFNULL(totsum, 0) totalsize
226
+ FROM (
227
+ SELECT schema_name, default_character_set_name charset, default_collation_name collation
228
+ FROM information_schema.SCHEMATA
229
+ WHERE schema_name='%s'
230
+ ) A LEFT JOIN (
231
+ SELECT db, COUNT(1) tables, SUM(dat) datsum, SUM(ndx) ndxsum, SUM(dat+ndx) totsum
232
+ FROM (
233
+ SELECT table_schema db, data_length dat, index_length ndx
234
+ FROM information_schema.tables WHERE engine IS NOT NULL
235
+ AND table_schema='%s'
236
+ ) AA
237
+ GROUP BY db
238
+ ) B ON A.schema_name=B.db''' % (_escape(dbname), _escape(dbname))
239
+ dbinfo = _sql(child, sql)
240
+ if dbinfo: dbinfo = dbinfo[0]
241
+
242
+ _exit(child)
243
+ return dbinfo
244
+
245
+ def create_database(pwd, dbname, charset='utf8', collation='utf8_general_ci'):
246
+ """Create a new database.
247
+ """
248
+ child = _mysql(pwd)
249
+ if not child: return False
250
+ sql = 'CREATE DATABASE %s CHARACTER SET %s COLLATE %s' % \
251
+ (_escape(dbname), _escape(charset), _escape(collation))
252
+ if not _sql(child, sql):
253
+ _exit(child)
254
+ return False
255
+ _exit(child)
256
+ return True
257
+
258
+ def alter_database(pwd, dbname, charset='utf8', collation='utf8_general_ci'):
259
+ """Alter database.
260
+ """
261
+ child = _mysql(pwd)
262
+ if not child: return False
263
+ sql = 'ALTER DATABASE %s CHARACTER SET %s COLLATE %s' % \
264
+ (_escape(dbname), _escape(charset), _escape(collation))
265
+ if not _sql(child, sql):
266
+ _exit(child)
267
+ return False
268
+ _exit(child)
269
+ return True
270
+
271
+ def rename_database(pwd, dbname, newname):
272
+ """Rename a database.
273
+ """
274
+ child = _mysql(pwd)
275
+ if not child: return False
276
+
277
+ try:
278
+ create_sql = _sql(child, 'SHOW CREATE DATABASE %s' % _escape(dbname), includefields=False)
279
+ if not create_sql: raise Exception()
280
+ create_sql = create_sql[0][1]
281
+ if not _sql(child, 'USE %s' % _escape(dbname)): raise Exception()
282
+ tables = _sql(child, 'SHOW TABLES', includefields=False)
283
+ if tables == False: raise Exception()
284
+
285
+ if not _sql(child, create_sql.replace(dbname, newname)): raise Exception()
286
+ for table in tables:
287
+ table = table[0]
288
+ sql = 'RENAME TABLE `%s`.`%s` TO `%s`.`%s`' % (_escape(dbname), table, _escape(newname), table)
289
+ if not _sql(child, sql): raise Exception()
290
+ if not _sql(child, 'DROP DATABASE %s' % _escape(dbname)): raise Exception()
291
+ except:
292
+ _exit(child)
293
+ return False
294
+ else:
295
+ _exit(child)
296
+ return True
297
+
298
+ def drop_database(pwd, dbname):
299
+ """Drop a database.
300
+ """
301
+ child = _mysql(pwd)
302
+ if not child: return False
303
+ if not _sql(child, 'DROP DATABASE %s' % _escape(dbname)):
304
+ _exit(child)
305
+ return False
306
+ _exit(child)
307
+ return True
308
+
309
+ def export_database(pwd, dbname, exportpath):
310
+ """Export database to a file.
311
+ """
312
+ filename = '%s_%s.sql' % (dbname, time.strftime('%Y%m%d_%H%M%S'))
313
+ filepath = str(Path(exportpath) / filename)
314
+ if not valid_filename(filename): return False
315
+
316
+ cmd = 'mysqldump -uroot -p %s' % dbname
317
+ cmd = '/bin/bash -c "%s > %s"' % (cmd, filepath)
318
+ child = pexpect.spawn(cmd)
319
+
320
+ i = child.expect(['Enter password', pexpect.EOF])
321
+ if i == 1:
322
+ if child.isalive(): child.wait()
323
+ return False
324
+
325
+ child.sendline(pwd)
326
+ i = child.expect(['error', pexpect.EOF])
327
+ if child.isalive():
328
+ w = child.wait()
329
+ return w == 0
330
+
331
+ return i != 0
332
+
333
+ def show_users(pwd, dbname=None):
334
+ """Show all user list, or user list of specified database.
335
+ """
336
+ child = _mysql(pwd)
337
+ if not child: return False
338
+
339
+ if not dbname:
340
+ sql = "SELECT *, IF(`Password` = _latin1 '', 'N', 'Y') AS 'Password' FROM `mysql`.`user` ORDER BY `User` ASC, `Host` ASC"
341
+ else:
342
+ sql = '''
343
+ (SELECT `User`, `Host`, `Select_priv`, `Insert_priv`, `Update_priv`, `Delete_priv`, `Create_priv`, `Drop_priv`,
344
+ `Grant_priv`, `Index_priv`, `Alter_priv`, `References_priv`, `Create_tmp_table_priv`, `Lock_tables_priv`,
345
+ `Create_view_priv`, `Show_view_priv`, `Create_routine_priv`, `Alter_routine_priv`, `Execute_priv`, `Event_priv`, `Trigger_priv`, `Db`
346
+ FROM `mysql`.`db` WHERE '%s' LIKE `Db` AND NOT (
347
+ `Select_priv` = 'N' AND `Insert_priv` = 'N' AND `Update_priv` = 'N' AND `Delete_priv` = 'N' AND `Create_priv` = 'N' AND `Drop_priv` = 'N'
348
+ AND `Grant_priv` = 'N' AND `References_priv` = 'N' AND `Create_tmp_table_priv` = 'N' AND `Lock_tables_priv` = 'N' AND `Create_view_priv` = 'N'
349
+ AND `Show_view_priv` = 'N' AND `Create_routine_priv` = 'N' AND `Alter_routine_priv` = 'N' AND `Execute_priv` = 'N' AND `Event_priv` = 'N'
350
+ AND `Trigger_priv` = 'N'
351
+ )) UNION (
352
+ SELECT `User`, `Host`, `Select_priv`, `Insert_priv`, `Update_priv`, `Delete_priv`, `Create_priv`, `Drop_priv`,
353
+ `Grant_priv`, `Index_priv`, `Alter_priv`, `References_priv`, `Create_tmp_table_priv`, `Lock_tables_priv`,
354
+ `Create_view_priv`, `Show_view_priv`, `Create_routine_priv`, `Alter_routine_priv`, `Execute_priv`, `Event_priv`, `Trigger_priv`, '*' AS `Db`
355
+ FROM `mysql`.`user` WHERE NOT (
356
+ `Select_priv` = 'N' AND `Insert_priv` = 'N' AND `Update_priv` = 'N' AND `Delete_priv` = 'N' AND `Create_priv` = 'N' AND `Drop_priv` = 'N'
357
+ AND `Grant_priv` = 'N' AND `References_priv` = 'N' AND `Create_tmp_table_priv` = 'N' AND `Lock_tables_priv` = 'N' AND `Create_view_priv` = 'N'
358
+ AND `Show_view_priv` = 'N' AND `Create_routine_priv` = 'N' AND `Alter_routine_priv` = 'N' AND `Execute_priv` = 'N' AND `Event_priv` = 'N'
359
+ AND `Trigger_priv` = 'N'
360
+ )) ORDER BY `User` ASC, `Host` ASC, `Db` ASC''' % _escape(dbname)
361
+
362
+ privs = _sql(child, sql)
363
+
364
+ _exit(child)
365
+ return privs
366
+
367
+ def show_user_globalprivs(pwd, user, host):
368
+ """Show user global privileges.
369
+ """
370
+ child = _mysql(pwd)
371
+ if not child: return False
372
+
373
+ sql = "SELECT * FROM `mysql`.`user` WHERE `User` = '%s' AND `Host` = '%s'" % (_escape(user), _escape(host))
374
+ privs = _sql(child, sql)
375
+ privs = privs and privs[0] or False
376
+
377
+ _exit(child)
378
+ return privs
379
+
380
+ def show_user_dbprivs(pwd, user, host, dbname=None):
381
+ """Show user privileges in database.
382
+ """
383
+ child = _mysql(pwd)
384
+ if not child: return False
385
+
386
+ if not dbname:
387
+ sql = "SELECT * FROM `mysql`.`db` WHERE `User` = '%s' AND `Host` = '%s' ORDER BY `Db` ASC" % (_escape(user), _escape(host))
388
+ else:
389
+ sql = "SELECT * FROM `mysql`.`db` WHERE `User` = '%s' AND `Host` = '%s' AND '%s' LIKE `Db`" % (_escape(user), _escape(host), _escape(dbname))
390
+
391
+ privs = _sql(child, sql)
392
+ if privs and dbname: privs = privs[0]
393
+
394
+ _exit(child)
395
+ return privs
396
+
397
+ def revoke_user_privs(pwd, user, host, dbname=None):
398
+ """Revoke user's privileges.
399
+ """
400
+ child = _mysql(pwd)
401
+ if not child: return False
402
+
403
+ if not dbname:
404
+ dbexpr = '*.*'
405
+ else:
406
+ dbexpr = '`%s`.*' % _escape(dbname)
407
+
408
+ try:
409
+ sql = "REVOKE ALL PRIVILEGES ON %s FROM '%s'@'%s'" % (dbexpr, _escape(user), _escape(host))
410
+ if not _sql(child, sql, False): raise Exception()
411
+ sql = "REVOKE GRANT OPTION ON %s FROM '%s'@'%s'" % (dbexpr, _escape(user), _escape(host))
412
+ if not _sql(child, sql, False): raise Exception()
413
+ if not dbname:
414
+ sql = "GRANT USAGE ON *.* TO '%s'@'%s' " \
415
+ "WITH MAX_QUERIES_PER_HOUR 0 " \
416
+ "MAX_CONNECTIONS_PER_HOUR 0 " \
417
+ "MAX_UPDATES_PER_HOUR 0 " \
418
+ "MAX_USER_CONNECTIONS 0" % (_escape(user), _escape(host))
419
+ if not _sql(child, sql, False): raise Exception()
420
+ except:
421
+ _exit(child)
422
+ return False
423
+ else:
424
+ _exit(child)
425
+ return True
426
+
427
+ def update_user_privs(pwd, user, host, privs, dbname=None):
428
+ """Update user's privileges.
429
+ """
430
+ child = _mysql(pwd)
431
+ if not child: return False
432
+
433
+ allprivs = [
434
+ 'SELECT', 'INSERT', 'UPDATE', 'DELETE',
435
+ 'CREATE', 'ALTER', 'INDEX', 'DROP', 'CREATE TEMPORARY TABLES',
436
+ 'SHOW VIEW', 'CREATE ROUTINE', 'ALTER ROUTINE', 'EXECUTE',
437
+ 'CREATE VIEW', 'EVENT', 'TRIGGER',
438
+ 'GRANT', 'LOCK TABLES', 'REFERENCES'
439
+ ]
440
+
441
+ if not dbname:
442
+ allprivs.extend([
443
+ 'FILE', 'SUPER', 'PROCESS', 'RELOAD', 'SHUTDOWN', 'SHOW DATABASES',
444
+ 'REPLICATION CLIENT', 'REPLICATION SLAVE', 'CREATE USER'
445
+ ])
446
+ dbexpr = '*.*'
447
+ else:
448
+ dbexpr = '`%s`.*' % _escape(dbname)
449
+
450
+ privs = filter(lambda a: a in allprivs, privs)
451
+
452
+ # revoke privileges
453
+ if len(privs) == 0:
454
+ _exit(child)
455
+ return revoke_user_privs(pwd, user, host, dbname)
456
+
457
+ if 'GRANT' in privs:
458
+ grant_option = 'WITH GRANT OPTION'
459
+ privs = filter(lambda a: a != 'GRANT', privs)
460
+ else:
461
+ grant_option = ''
462
+ if len(privs) == len(allprivs)-1:
463
+ grant_sql = "GRANT ALL PRIVILEGES ON %s TO '%s'@'%s' %s" % (dbexpr, _escape(user), _escape(host), grant_option)
464
+ else:
465
+ grant_sql = "GRANT %s ON %s TO '%s'@'%s' %s" % (','.join(privs), dbexpr, _escape(user), _escape(host), grant_option)
466
+
467
+ try:
468
+ # don't check revoke result, the user may not exists
469
+ sql = "REVOKE ALL PRIVILEGES ON %s FROM '%s'@'%s'" % (dbexpr, _escape(user), _escape(host))
470
+ _sql(child, sql, False)
471
+ sql = "REVOKE GRANT OPTION ON %s FROM '%s'@'%s'" % (dbexpr, _escape(user), _escape(host))
472
+ _sql(child, sql, False)
473
+ if not _sql(child, grant_sql, False): raise Exception()
474
+ except:
475
+ _exit(child)
476
+ return False
477
+ else:
478
+ _exit(child)
479
+ return True
480
+
481
+ def create_user(pwd, user, host, password=None):
482
+ """Create a user.
483
+ """
484
+ child = _mysql(pwd)
485
+ if not child: return False
486
+
487
+ if password:
488
+ sql = "CREATE USER '%s'@'%s' IDENTIFIED BY '%s'" % (_escape(user), _escape(host), _escape(password))
489
+ else:
490
+ sql = "CREATE USER '%s'@'%s'" % (_escape(user), _escape(host))
491
+ rs = _sql(child, sql, False)
492
+
493
+ _exit(child)
494
+ return rs
495
+
496
+ def drop_user(pwd, user, host):
497
+ """Drop a user.
498
+ """
499
+ child = _mysql(pwd)
500
+ if not child:
501
+ return False
502
+
503
+ rs = _sql(child, "DROP USER '%s'@'%s'" % (_escape(user), _escape(host)), False)
504
+
505
+ _exit(child)
506
+ return rs
507
+
508
+ def set_user_password(pwd, user, host, password=None):
509
+ """Set password for user.
510
+ """
511
+ child = _mysql(pwd)
512
+ if not child: return False
513
+
514
+ if password:
515
+ sql = "SET PASSWORD FOR '%s'@'%s' = PASSWORD('%s')" % (_escape(user), _escape(host), _escape(password))
516
+ else:
517
+ sql = "SET PASSWORD FOR '%s'@'%s' = ''" % (_escape(user), _escape(host))
518
+ rs = _sql(child, sql, False)
519
+
520
+ _exit(child)
521
+ return rs
522
+
523
+
524
+ def web_handler(context):
525
+ action = context.get_argument('action', '')
526
+ password = context.get_argument('password', '')
527
+
528
+ if action == 'updatepwd':
529
+ newpassword = context.get_argument('newpassword', '')
530
+ newpasswordc = context.get_argument('newpasswordc', '')
531
+
532
+ if newpassword != newpasswordc:
533
+ context.write({'code': -1, 'msg': '两次密码输入不一致!'})
534
+ return
535
+
536
+ if updatepwd(newpassword, password):
537
+ context.write({'code': 0, 'msg': '密码设置成功!'})
538
+ else:
539
+ context.write({'code': -1, 'msg': '密码设置失败!'})
540
+
541
+ elif action == 'checkpwd':
542
+ if checkpwd(password):
543
+ context.write({'code': 0, 'msg': '密码验证成功!'})
544
+ else:
545
+ context.write({'code': -1, 'msg': '密码验证失败!(密码不正确,或 MySQL 服务未启动)'})
546
+
547
+ elif action == 'alter_database':
548
+ dbname = context.get_argument('dbname', '')
549
+ collation = context.get_argument('collation', '')
550
+ rt = alter_database(password, dbname, collation=collation)
551
+ if rt:
552
+ context.write({'code': 0, 'msg': '数据库编码保存成功!'})
553
+ else:
554
+ context.write({'code': -1, 'msg': '数据库编码保存失败!'})
555
+
556
+
557
+ if __name__ == '__main__':
558
+ import pprint
559
+ pp = pprint.PrettyPrinter(indent=4)
560
+
561
+ #pp.pprint(checkpwd('admin'))
562
+ #pp.pprint(show_databases('admin'))
563
+ #pp.pprint(show_database('admin', 'abcd'))
564
+ #pp.pprint(create_database('admin', 'abcd'))
565
+ #pp.pprint(alter_database('admin', 'abcd', 'latin1', 'latin1_swedish_ci'))
566
+ #pp.pprint(drop_database('admin', 'abcd'))
567
+ #pp.pprint(rename_database('admin', 'abcd', 'test'))
568
+ #pp.pprint(export_database('admin', 'abcd', '/root'))
569
+ #pp.pprint(show_users('admin', 'abcd'))
570
+ #pp.pprint(show_users('admin'))
571
+ #pp.pprint(show_user_globalprivs('admin', 'ddyh', 'localhost'))
572
+ #pp.pprint(show_user_dbprivs('admin', 'ddyh', 'localhost'))
573
+ #pp.pprint(revoke_user_privs('admin', 'ddyh', 'localhost', 'abcd'))
574
+ #pp.pprint(update_user_privs('admin', 'hilyjiang', 'localhost', ['SELECT']))
575
+ #pp.pprint(create_user('admin', 'hilyjiang', '%'))
576
+ #pp.pprint(drop_user('admin', 'hilyjiang', 'localhost'))
577
+ #pp.pprint(set_user_password('admin', 'ddyh', 'localhost', 'ddyh'))