Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c625dd881 | ||
|
|
30194479d1 | ||
|
|
ddeced3540 | ||
|
|
dc43b6973a | ||
|
|
d1013c2416 | ||
|
|
f7be2bc2e6 | ||
|
|
42d1e35913 | ||
|
|
74f512d214 | ||
|
|
b945069117 | ||
|
|
efcbff23d8 | ||
|
|
dcabfb1546 | ||
|
|
9e6d758c0f | ||
|
|
1da20feb06 | ||
|
|
3044c9b735 | ||
|
|
b44e4891a6 | ||
|
|
cbbf3ba0ba |
@@ -2,9 +2,10 @@
|
||||
|
||||
# local files
|
||||
qqwry.dat
|
||||
revsuit.db
|
||||
revsuit.db-shm
|
||||
revsuit.db-wal
|
||||
GeoLite2-City.mmdb
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
# local config
|
||||
config.yaml*
|
||||
@@ -20,3 +21,8 @@ revsuit
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
## node
|
||||
package.json
|
||||
package-lock.json
|
||||
node_modules/
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.1.5-beta](https://github.com/Li4n0/revsuit/compare/v0.1.4-beta-fix-1...v0.1.5-beta) (2021-06-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ipinfo:** support GeoIP ([#26](https://github.com/Li4n0/revsuit/issues/26)) ([dc43b69](https://github.com/Li4n0/revsuit/commit/dc43b6973a5ac98e439d1353102ade2029b1d382))
|
||||
* **platform:** support custom admin path prefix ([#25](https://github.com/Li4n0/revsuit/issues/25)) ([f7be2bc](https://github.com/Li4n0/revsuit/commit/f7be2bc2e67841178e9316995999b20f80a49df7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** fix the bug of autoRefresh ([#27](https://github.com/Li4n0/revsuit/issues/27)) ([d1013c2](https://github.com/Li4n0/revsuit/commit/d1013c2416c04bd72b9cbb0c7e6b4b3f2e717837))
|
||||
* **frontend:** fix the frontend display bugs ([#28](https://github.com/Li4n0/revsuit/issues/28)) ([ddeced3](https://github.com/Li4n0/revsuit/commit/ddeced354042e0be6db5dd4feffa13ef22db231f))
|
||||
|
||||
### [0.1.4-beta-fix-1](https://github.com/Li4n0/revsuit/compare/v0.1.4-beta...v0.1.4-beta-fix-1) (2021-05-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** fix the problem of autoRefresh failure ([#24](https://github.com/Li4n0/revsuit/issues/24)) ([b945069](https://github.com/Li4n0/revsuit/commit/b945069117fec6eb5de88557da5d4c2c996cfd90))
|
||||
* **http:** fix the problem that ip_header setting does not take effect ([#23](https://github.com/Li4n0/revsuit/issues/23)) ([74f512d](https://github.com/Li4n0/revsuit/commit/74f512d2140fb97128acf56be803d1bd3b888fa3))
|
||||
|
||||
### [0.1.4-beta](https://github.com/Li4n0/revsuit/compare/v0.1.3-beta-fix1...v0.1.4-beta) (2021-05-27)
|
||||
|
||||
### Features
|
||||
|
||||
* **dns:** support custom dns service
|
||||
port ([7fb4c97](https://github.com/Li4n0/revsuit/commit/7fb4c97279e57d120a4e4aef5dda5c8f3c024835))
|
||||
* **frontend:** auto refresh data when switching back to the revsuit
|
||||
console ([9b31406](https://github.com/Li4n0/revsuit/commit/9b314062a39ddc7acf7a7eab3570b24b9bb9d122))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **http:** fix the problem that ip_header configuration does not take
|
||||
effect ([b76c39e](https://github.com/Li4n0/revsuit/commit/b76c39e2fc1ada189feb783fdec76daffa11d1c7))
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2021 Li4n0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,4 +1,246 @@
|
||||
# RevSuit - A Reverse Platform Developed in Golang
|
||||
# RevSuit - A Flexible and Powerful Reverse Connection Platform
|
||||
|
||||
## Overview
|
||||
RevSuit is an open-sourced Reverse Platform designed for receive connection from target host in bug bounty.
|
||||
|
||||
RevSuit is a flexible and powerful reverse connection platform designed for receiving connection from target host in
|
||||
penetration. It currently supports HTTP, DNS, RMI, MySQL and FTP protocols.
|
||||
|
||||
Flexible:
|
||||
|
||||
1. Revsuit can set flexible rules to capture different connections.
|
||||
2. Deploy and run via binaries without worrying about nasty dependency errors.
|
||||
|
||||
Powerful:
|
||||
|
||||
1. Revsuit allows users to customize the response for different connections through rules and supports dynamic response
|
||||
generation using template variables.
|
||||
2. Support a variety of protocols, and support the in-depth utilization of some protocols, such as DNS rebinding, MySQL
|
||||
Load Local Data, JDBC Deserialize Exploit, FTP Passive Mode SSRF, etc.
|
||||
3. Native support for use with scanners.
|
||||
4. Support push notifications to popular office software.
|
||||
5. Other thoughtful and detailed features.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Preparation
|
||||
|
||||
Download the latest release firstly.
|
||||
|
||||
RevSuit will generate default configuration file on first run. Modify the configuration file according to your needs,
|
||||
then re-run.
|
||||
|
||||
In order to confirm the IP location, you need to use the IP location database. `QQwry` is used as the data source by
|
||||
default, you can also modify the configuration to use `GeoIP`. If the selected database is not available in the current
|
||||
directory or the database is updated for more than a week, RevSuit will automatically download the latest database. If
|
||||
the download fails, the `IpArea` field will always be null.
|
||||
|
||||
```bash
|
||||
$ ./revsuit
|
||||
2021/05/16 22:55:10 [ INFO] Downloading qqwry.dat...
|
||||
|
||||
____ _____ _ __
|
||||
/ __ \___ _ __/ ___/__ __(_) /_
|
||||
/ /_/ / _ \ | / /\__ \/ / / / / __/
|
||||
/ _, _/ __/ |/ /___/ / /_/ / / /_
|
||||
/_/ |_|\___/|___//____/\__,_/_/\__/
|
||||
vBeta0.1
|
||||
https://revsuit.pro
|
||||
2021/05/16 22:55:22 [ INFO] Starting HTTP Server at :80, token:your_token
|
||||
2021/05/16 22:55:22 [ INFO] Start to listen FTP PASV port at :2020, PasvIP is 10.9.8.7
|
||||
2021/05/16 22:55:22 [ INFO] Starting FTP Server at :21
|
||||
2021/05/16 22:55:22 [ INFO] Starting MySQL Server at :3306
|
||||
2021/05/16 22:55:22 [ INFO] Starting RMI Server at :1099
|
||||
2021/05/16 22:55:22 [ INFO] Starting DNS Server at :53
|
||||
```
|
||||
|
||||
After running, you can visit the `/revsuit/admin/` path of HTTP Server and enter the token to access the control panel.
|
||||
|
||||
### Create Rules and Receive Connection
|
||||
|
||||
Take the creation of HTTP rules as an example:
|
||||
|
||||
A few notes are as follows:
|
||||
|
||||
1. The `name` and `flagFormat` of rule are unique.
|
||||
2. `FlagFormat` uses regular expression syntax, and for different protocols, the fields matched by flagFormat are
|
||||
different, you can check the corresponding hints to get details when creating rules.
|
||||
3. Rules with high rank will take effect first.
|
||||
4. For different protocol rules, you can make different custom configurations, and for some protocols, template
|
||||
variables can be used.
|
||||
5. You can set the named grouping of regular expressions in `flagFormat`, and the result of the matching grouping will
|
||||
also be used as a template variable.
|
||||
|
||||
As shown below, we create a rule that uses the built-in template variables and custom template variables of the http
|
||||
protocol, and names it `test_create_rule`:
|
||||
|
||||

|
||||
|
||||
Then make a request that satisfies the rule and view the response.
|
||||
|
||||

|
||||
|
||||
The request will be logged on the platform at the same time.
|
||||
|
||||

|
||||
|
||||
### Notice
|
||||
|
||||
If you want to be notified of new connections on your office software, you can configure the webhook address of the
|
||||
relevant software in the configuration file and turn on the `Notice` switch for the corresponding rule. Only support to
|
||||
four software types currently: dingtalk,wechat,lark,slack.(Discord and telegram support is in the plan.)
|
||||
|
||||
### Import/Export Rules
|
||||
|
||||
If you're migrating platforms or purging data, it can be a hassle to recreate rules. That's why the platform supports
|
||||
import and export of rules.
|
||||
|
||||
The entry point for this function is located at `Settings>RULES`.
|
||||
|
||||
The rules are stored in yaml format for import and export, like the following:
|
||||
|
||||
```yaml
|
||||
http:
|
||||
- name: test_create_rule
|
||||
flag_format: (?P<what>\w+)\?
|
||||
rank: 0
|
||||
push_to_client: false
|
||||
notice: false
|
||||
response_status_code: "302"
|
||||
response_headers:
|
||||
Location: ${query.url}
|
||||
response_body: ${header.say} ${what}
|
||||
- name: other_rule
|
||||
flag_format: other
|
||||
rank: 1
|
||||
push_to_client: false
|
||||
notice: true
|
||||
response_status_code: "200"
|
||||
response_headers: { }
|
||||
response_body: Hello Revsuit!
|
||||
dns:
|
||||
... ...
|
||||
```
|
||||
|
||||
## Advance Usage
|
||||
|
||||
### Use with Scanner
|
||||
|
||||
RevSuit was split from my scanner project, so its native support works with scanners.
|
||||
|
||||
From RevSuit's perspective, we call a scanner a client.
|
||||
|
||||
#### Create Connection
|
||||
|
||||
RevSuit
|
||||
uses [HTTP Server-sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
|
||||
(SSE) to establish a one-way communication channel with the client.
|
||||
|
||||
The API for the channel is: `/revsuit/api/events?message` .The client first needs to add the `Token: your token` header
|
||||
to the Header, and then access the API to establish the channel. When the platform receives a new connection, the `flag`
|
||||
captured by the rule will be passed to the client through this channel.
|
||||

|
||||
|
||||
Here is a [simple demo](https://gist.github.com/Li4n0/21aa0bec2d626114a729ca2677efb05a) using Golang's sse library as an
|
||||
example.
|
||||
|
||||
#### Multi-client
|
||||
|
||||
As shown above, RevSuit supports multiple clients, and each client in the connected state receives a push of `flag`, so
|
||||
distributed scanning can be supported.
|
||||
|
||||
#### Temporary storage queue
|
||||
|
||||
RevSuit will store `flag` temporarily in the queue when there is no client connection and send it when the client
|
||||
connects, so you don't have to worry about missing the vulnerability because the client disconnects. (This is especially
|
||||
helpful for discovering delay-triggered vulnerabilities.)
|
||||
|
||||
#### Use flagGroup
|
||||
|
||||
`FlagGroup` is the content matched by the anonymous group in the `flagFormat` field of the rule. The platform will check
|
||||
the content matched in the grouping,and the flag is only pushed to the client when then content(`flagGroup`) captured
|
||||
for the first time.
|
||||
|
||||
In a real-world vulnerability scanning scenario, you may send a large number of different payloads for a single
|
||||
vulnerability point, and they may all be valid, which can result in the backlink platform receiving many requests, yet
|
||||
they are caused by the same vulnerability. If you don't want the client to receive so many `flags` for the same
|
||||
vulnerability, you can take advantage of the `flagGroup` feature of rule's flagFormat.
|
||||
|
||||
For example, SSRF scanning.
|
||||
|
||||
Firstly create a rule like the following:
|
||||
|
||||
```yaml
|
||||
http:
|
||||
- name: ssrf
|
||||
flag_format: (ssrf[a-z0-9]{6})[0-9]{1,3}
|
||||
rank: 0
|
||||
push_to_client: false
|
||||
notice: false
|
||||
response_status_code: "200"
|
||||
response_headers: { }
|
||||
response_body: "Here is a SSRF!"
|
||||
```
|
||||
|
||||
Suppose our target is `https://www.testvuln.com?url=api.com&p=useless`, and for SSRF we have 5 payloads. The final
|
||||
request sent by the scanner may
|
||||
be `['https://www.testvuln.com?url=http://revsuit.com/ssrfa98oni1&p=useless, ... ,'https://www.testvuln.com?url=//revsuit.com/ssrfa98oni5&p=useless`']
|
||||
. They may all attack successfully.
|
||||
|
||||
However, because anonymous grouping is used in the rule's `flagFormat`, The platform will query the
|
||||
connected `flagGroup`, in this case `ssrfa98oni`, and push `flag` to the client only at its first appearance, so the
|
||||
client will only receive one flag:`ssrfa98oni1`. But this is enough, because it already proves that the `url` parameter
|
||||
of the target is vulnerable.
|
||||
|
||||
### Multi-module collocation
|
||||
|
||||
In actual penetration testing scenarios, certain tasks can be done easily and quickly by combining and matching various
|
||||
modules of RevSuit. The following is an example of a blind XXE scenario, showing how to use RevSuit's HTTP and FTP
|
||||
modules, combined with template variables, to quickly complete a port scan.
|
||||
|
||||
First use create an HTTP rule to return evil.dtd, customize the response to the contents of dtd so that it goes to
|
||||
connect to RevSuit's FTP service, and use template variables to pass the Host and Port to be scanned to FTP via FTP's
|
||||
user and password.
|
||||
|
||||

|
||||
|
||||
Then create an FTP rule that receives the Host and port to be scanned from the user and password template variables, set
|
||||
to Pasv Address.
|
||||
|
||||

|
||||
|
||||
Then use BurpSuit to launch the scan, by setting the host and port parameters in the evil.dtd URL to set the target of
|
||||
the port scan.
|
||||
|
||||

|
||||
|
||||
The running effect is as follows:
|
||||
|
||||

|
||||
|
||||
It because the FTP connection will be crashed if `Passive Address` is not accessible, we can determine whether the port
|
||||
is open or not based on whether the connection exits normally.For this example, we successfully detected that port 8005
|
||||
and 8080 are open.
|
||||
|
||||
## More Usage
|
||||
|
||||
A more detailed wiki is being prepared, you can explore by yourself before it.
|
||||
|
||||
## Advice and Communication
|
||||
|
||||
Submit an issue or contact me through Weixin: `Li4n06`
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
### Reference
|
||||
|
||||
* [https://github.com/rmb122/rogue_mysql_server](https://github.com/rmb122/rogue_mysql_server)
|
||||
* [https://github.com/256dpi/newdns](https://github.com/256dpi/newdns)
|
||||
|
||||
### Specifically
|
||||
|
||||
Thanks to my friend [@E99p1ant](https://github.com/wuhan005/) for all the help and advice I received during the
|
||||
development of this project.
|
||||
|
||||
## License
|
||||
|
||||
@Apache License 2.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
.custom-icons-list[data-v-05a908c2] .anticon{margin-right:6px}body[data-v-4ae4fda9],html[data-v-4ae4fda9]{height:100%;margin:0}.fade-transform-enter-active[data-v-4ae4fda9],.fade-transform-leave-active[data-v-4ae4fda9]{transition:all .3s;opacity:0}.fade-transform-enter[data-v-4ae4fda9],.fade-transform-leave[data-v-4ae4fda9]{opacity:0}.fade-transform-enter-to[data-v-4ae4fda9]{opacity:0}.ant-menu-item>span>a[data-v-4ae4fda9]{color:hsla(0,0%,100%,.65)}.ant-menu-item-selected>span>a[data-v-4ae4fda9]{color:#fff}#nav[data-v-4ae4fda9]{height:100%}#nav .trigger[data-v-4ae4fda9]{font-size:18px;line-height:64px;padding:0 24px;cursor:pointer;transition:color .3s}#nav .trigger[data-v-4ae4fda9]:hover{color:#1890ff}#nav .logo[data-v-4ae4fda9]{height:32px;background:#0a1d2d;margin:16px;text-align:center;font-size:1.2rem;color:#fff;padding-bottom:5px;border-bottom:2px solid #b6befa}.copyright[data-v-4ae4fda9]{color:#888;text-align:right;margin-right:1rem}
|
||||
.custom-icons-list[data-v-05a908c2] .anticon{margin-right:6px}body[data-v-1bc6d13e],html[data-v-1bc6d13e]{height:100%;margin:0}.fade-transform-enter-active[data-v-1bc6d13e],.fade-transform-leave-active[data-v-1bc6d13e]{transition:all .3s;opacity:0}.fade-transform-enter[data-v-1bc6d13e],.fade-transform-leave[data-v-1bc6d13e]{opacity:0}.fade-transform-enter-to[data-v-1bc6d13e]{opacity:0}.ant-menu-item>span>a[data-v-1bc6d13e]{color:hsla(0,0%,100%,.65)}.ant-menu-item-selected>span>a[data-v-1bc6d13e]{color:#fff}#app[data-v-1bc6d13e]{min-height:100%}#app .trigger[data-v-1bc6d13e]{font-size:18px;line-height:64px;padding:0 24px;cursor:pointer;transition:color .3s}#app .trigger[data-v-1bc6d13e]:hover{color:#1890ff}#app .logo[data-v-1bc6d13e]{height:32px;background:#0a1d2d;margin:16px;text-align:center;font-size:1.2rem;color:#fff;padding-bottom:5px;border-bottom:2px solid #b6befa}.copyright[data-v-1bc6d13e]{color:#888;text-align:right;margin-right:1rem}
|
||||
@@ -0,0 +1 @@
|
||||
#add-rule[data-v-50ca3b7c]{margin-bottom:10px}
|
||||
@@ -0,0 +1 @@
|
||||
#add-rule[data-v-4fed8448]{margin-bottom:10px}
|
||||
@@ -1 +0,0 @@
|
||||
#add-rule[data-v-ae6501fc]{margin-bottom:10px}
|
||||
@@ -0,0 +1 @@
|
||||
#add-rule[data-v-1f4c0376]{margin-bottom:10px}.full-screen-icon[data-v-1f4c0376]{position:absolute;z-index:5;color:#9e9e9e;right:1px;font-size:1rem;cursor:pointer;transition:font-size .1s}.full-screen-icon[data-v-1f4c0376]:hover{font-size:1.1rem;transition:font-size .1s}
|
||||
@@ -1 +0,0 @@
|
||||
#add-rule[data-v-5aa75eae]{margin-bottom:10px}
|
||||
@@ -0,0 +1 @@
|
||||
#add-rule[data-v-1361da51]{margin-bottom:10px}
|
||||
@@ -0,0 +1 @@
|
||||
#add-rule[data-v-6f29426c]{margin-bottom:10px}
|
||||
@@ -1 +0,0 @@
|
||||
#add-rule[data-v-4bcc6b83]{margin-bottom:10px}
|
||||
@@ -1 +0,0 @@
|
||||
#add-rule[data-v-79ad429e]{margin-bottom:10px}.full-screen-icon[data-v-79ad429e]{position:absolute;z-index:5;color:#9e9e9e;right:1px;font-size:1rem;cursor:pointer;transition:font-size .1s}.full-screen-icon[data-v-79ad429e]:hover{font-size:1.1rem;transition:font-size .1s}
|
||||
@@ -1 +0,0 @@
|
||||
#add-rule[data-v-376729b6]{margin-bottom:10px}
|
||||
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>RevSuit Control Panel</title><link href="css/chunk-1da7f23b.css" rel="prefetch"><link href="css/chunk-330a7e95.css" rel="prefetch"><link href="css/chunk-36391b77.css" rel="prefetch"><link href="css/chunk-3d13e56e.css" rel="prefetch"><link href="css/chunk-42105840.css" rel="prefetch"><link href="css/chunk-65a35ecf.css" rel="prefetch"><link href="css/chunk-7bcb83e6.css" rel="prefetch"><link href="css/chunk-b8f538aa.css" rel="prefetch"><link href="css/chunk-e039e75a.css" rel="prefetch"><link href="css/chunk-fef965c2.css" rel="prefetch"><link href="js/chunk-08f9fbe8.js" rel="prefetch"><link href="js/chunk-1da7f23b.js" rel="prefetch"><link href="js/chunk-330a7e95.js" rel="prefetch"><link href="js/chunk-36391b77.js" rel="prefetch"><link href="js/chunk-3d13e56e.js" rel="prefetch"><link href="js/chunk-42105840.js" rel="prefetch"><link href="js/chunk-65a35ecf.js" rel="prefetch"><link href="js/chunk-7bcb83e6.js" rel="prefetch"><link href="js/chunk-b241c712.js" rel="prefetch"><link href="js/chunk-b8f538aa.js" rel="prefetch"><link href="js/chunk-e039e75a.js" rel="prefetch"><link href="js/chunk-fef965c2.js" rel="prefetch"><link href="css/app.css" rel="preload" as="style"><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="js/app.js" rel="preload" as="script"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link href="css/app.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but revsuit-frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.js"></script><script src="js/app.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>RevSuit Control Panel</title><link href="/revsuit/admin/css/chunk-016c45cf.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-0cb5e9e6.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-193ec343.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-36508a38.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-43748c28.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-46a53d26.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-49fbda44.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-5fb70d83.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-72839f90.css" rel="prefetch"><link href="/revsuit/admin/css/chunk-740736c7.css" rel="prefetch"><link href="/revsuit/admin/js/chunk-016c45cf.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-0cb5e9e6.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-193ec343.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-27186dd0.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-36508a38.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-43748c28.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-46a53d26.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-49fbda44.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-5fb70d83.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-72839f90.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-740736c7.js" rel="prefetch"><link href="/revsuit/admin/js/chunk-b241c712.js" rel="prefetch"><link href="/revsuit/admin/css/app.css" rel="preload" as="style"><link href="/revsuit/admin/css/chunk-vendors.css" rel="preload" as="style"><link href="/revsuit/admin/js/app.js" rel="preload" as="script"><link href="/revsuit/admin/js/chunk-vendors.js" rel="preload" as="script"><link href="/revsuit/admin/css/chunk-vendors.css" rel="stylesheet"><link href="/revsuit/admin/css/app.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but revsuit-frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/revsuit/admin/js/chunk-vendors.js"></script><script src="/revsuit/admin/js/app.js"></script></body></html>
|
||||
@@ -5,13 +5,15 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "revsuit-frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^1.2.4",
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-router": "^3.2.0"
|
||||
"vue-router": "^3.2.0",
|
||||
"vue-visibility-change": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
@@ -4808,7 +4810,8 @@
|
||||
"node_modules/de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npm.taobao.org/de-indent/download/de-indent-1.0.2.tgz",
|
||||
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
|
||||
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.1",
|
||||
@@ -6896,6 +6899,7 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npm.taobao.org/he/download/he-1.2.0.tgz",
|
||||
"integrity": "sha1-hK5l+n6vsWX922FWauFLrwVmTw8=",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
@@ -13517,6 +13521,7 @@
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npm.taobao.org/vue-template-compiler/download/vue-template-compiler-2.6.12.tgz",
|
||||
"integrity": "sha1-lH7XGWdEyKUoXr4SM/6WBDf8xX4=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.1.0"
|
||||
@@ -13528,6 +13533,11 @@
|
||||
"integrity": "sha1-HuO8mhbsv1EYvjNLsV+cRvgvWCU=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vue-visibility-change": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npm.taobao.org/vue-visibility-change/download/vue-visibility-change-1.2.1.tgz",
|
||||
"integrity": "sha1-0YAIv08d4slHr+VEHuBEuenGAUE="
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npm.taobao.org/warning/download/warning-4.0.3.tgz",
|
||||
@@ -16426,8 +16436,7 @@
|
||||
"version": "4.5.11",
|
||||
"resolved": "https://registry.npm.taobao.org/@vue/cli-plugin-vuex/download/@vue/cli-plugin-vuex-4.5.11.tgz?cache=0&sync_timestamp=1615453605559&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fcli-plugin-vuex%2Fdownload%2F%40vue%2Fcli-plugin-vuex-4.5.11.tgz",
|
||||
"integrity": "sha1-9vYZvPtmyGzEU0DXMVKERjXlSL0=",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"dev": true
|
||||
},
|
||||
"@vue/cli-service": {
|
||||
"version": "4.5.11",
|
||||
@@ -16581,8 +16590,7 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npm.taobao.org/@vue/preload-webpack-plugin/download/@vue/preload-webpack-plugin-1.1.2.tgz",
|
||||
"integrity": "sha1-zrkktOyzucQ4ccekKaAvhCPmIas=",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"dev": true
|
||||
},
|
||||
"@vue/web-component-wrapper": {
|
||||
"version": "1.3.0",
|
||||
@@ -16797,8 +16805,7 @@
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npm.taobao.org/acorn-jsx/download/acorn-jsx-5.3.1.tgz",
|
||||
"integrity": "sha1-/IZh4Rt6wVOcR9v+oucrOvNNJns=",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"dev": true
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "7.2.0",
|
||||
@@ -16846,15 +16853,13 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npm.taobao.org/ajv-errors/download/ajv-errors-1.0.1.tgz?cache=0&sync_timestamp=1615411237786&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv-errors%2Fdownload%2Fajv-errors-1.0.1.tgz",
|
||||
"integrity": "sha1-81mGrOuRr63sQQL72FAUlQzvpk0=",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"dev": true
|
||||
},
|
||||
"ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.5.2.tgz?cache=0&sync_timestamp=1615647295573&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv-keywords%2Fdownload%2Fajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha1-MfKdpatuANHC0yms97WSlhTVAU0=",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"dev": true
|
||||
},
|
||||
"alphanum-sort": {
|
||||
"version": "1.0.2",
|
||||
@@ -18894,7 +18899,8 @@
|
||||
"de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npm.taobao.org/de-indent/download/de-indent-1.0.2.tgz",
|
||||
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
|
||||
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.1",
|
||||
@@ -20627,7 +20633,8 @@
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npm.taobao.org/he/download/he-1.2.0.tgz",
|
||||
"integrity": "sha1-hK5l+n6vsWX922FWauFLrwVmTw8="
|
||||
"integrity": "sha1-hK5l+n6vsWX922FWauFLrwVmTw8=",
|
||||
"dev": true
|
||||
},
|
||||
"hex-color-regex": {
|
||||
"version": "1.1.0",
|
||||
@@ -26181,6 +26188,7 @@
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npm.taobao.org/vue-template-compiler/download/vue-template-compiler-2.6.12.tgz",
|
||||
"integrity": "sha1-lH7XGWdEyKUoXr4SM/6WBDf8xX4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.1.0"
|
||||
@@ -26192,6 +26200,11 @@
|
||||
"integrity": "sha1-HuO8mhbsv1EYvjNLsV+cRvgvWCU=",
|
||||
"dev": true
|
||||
},
|
||||
"vue-visibility-change": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npm.taobao.org/vue-visibility-change/download/vue-visibility-change-1.2.1.tgz",
|
||||
"integrity": "sha1-0YAIv08d4slHr+VEHuBEuenGAUE="
|
||||
},
|
||||
"warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npm.taobao.org/warning/download/warning-4.0.3.tgz",
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-router": "^3.2.0"
|
||||
"vue-router": "^3.2.0",
|
||||
"vue-visibility-change": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a-layout id="nav">
|
||||
<a-layout id="app" v-visibility-change="()=>{this.$refs.content.fetch?this.$refs.content.fetch():null}">
|
||||
<a-layout-sider v-model="collapsed" :trigger="null" collapsible>
|
||||
<div class="logo"><b>R</b><span v-if="!collapsed"><b>ev</b>Suit</span></div>
|
||||
<a-menu theme="dark" mode="inline" :selectedKeys="[this.$route.path]" :openKeys.sync="openKeys">
|
||||
@@ -148,8 +148,8 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.autoRefresh && this.isLogMode) {
|
||||
this.timing()
|
||||
if (localStorage.getItem("autoRefresh") === null) {
|
||||
this.autoRefresh = true;
|
||||
}
|
||||
this.GetVersion()
|
||||
},
|
||||
@@ -174,9 +174,9 @@ export default {
|
||||
localStorage.setItem('autoRefresh', val)
|
||||
},
|
||||
isLogMode(val) {
|
||||
if (!val) {
|
||||
if (!val && this.timer) {
|
||||
clearInterval(this.timer)
|
||||
} else {
|
||||
} else if (this.autoRefresh) {
|
||||
this.timing()
|
||||
}
|
||||
},
|
||||
@@ -246,11 +246,11 @@ html, body {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#nav {
|
||||
height: 100%;
|
||||
#app {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
#nav .trigger {
|
||||
#app .trigger {
|
||||
font-size: 18px;
|
||||
line-height: 64px;
|
||||
padding: 0 24px;
|
||||
@@ -258,11 +258,11 @@ html, body {
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
#nav .trigger:hover {
|
||||
#app .trigger:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
#nav .logo {
|
||||
#app .logo {
|
||||
height: 32px;
|
||||
background: #0a1d2d;
|
||||
margin: 16px;
|
||||
|
||||
@@ -2,7 +2,7 @@ import axios from 'axios'
|
||||
import {store} from "@/main";
|
||||
|
||||
const service = axios.create({
|
||||
baseURL: '/revsuit/api/', // api的base_url
|
||||
baseURL: location.pathname.slice(0, -'/admin/'.length) + "/api", // api的base_url
|
||||
timeout: 5000, // request timeout
|
||||
validateStatus: function (status) {
|
||||
if (status === 403) {
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
<a-select-option value='error'>ERROR</a-select-option>
|
||||
<a-select-option value='fatal'>FATAL</a-select-option>
|
||||
</a-select>
|
||||
<a-input :disabled="key ==='Addr' && parent==='Http'" v-else v-model="form[key]"/>
|
||||
<a-input :disabled="(key ==='Addr'||key==='AdminPathPrefix') && parent==='Http'" v-else
|
||||
v-model="form[key]"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
@@ -4,10 +4,14 @@ import App from './App';
|
||||
import router from "@/router";
|
||||
import 'ant-design-vue/dist/antd.css';
|
||||
import '@/utils/index'
|
||||
import visibility from 'vue-visibility-change';
|
||||
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
Vue.use(Antd);
|
||||
Vue.use(visibility);
|
||||
|
||||
export const store = Vue.observable({
|
||||
authed: true,
|
||||
pageSize: localStorage.getItem("pageSize") ? parseInt(localStorage.getItem("pageSize")) : 10
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -139,14 +140,9 @@ export default {
|
||||
this.pagination = pagination;
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
});
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -228,14 +229,9 @@ export default {
|
||||
this.pagination = pagination;
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
});
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -104,9 +105,9 @@ const columns = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'PATH',
|
||||
dataIndex: 'path',
|
||||
key: 'path',
|
||||
title: 'URI',
|
||||
dataIndex: 'uri',
|
||||
key: 'uri',
|
||||
ellipsis: true,
|
||||
scopedSlots: {
|
||||
filterDropdown: 'filterDropdown',
|
||||
@@ -173,14 +174,9 @@ export default {
|
||||
this.pagination = pagination;
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
});
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -120,6 +121,15 @@ const columns = [
|
||||
filterIcon: 'filterIcon',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'SCHEMA',
|
||||
dataIndex: 'schema',
|
||||
key: 'schema',
|
||||
scopedSlots: {
|
||||
filterDropdown: 'filterDropdown',
|
||||
filterIcon: 'filterIcon',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'LOAD DATA',
|
||||
dataIndex: 'load_local_data',
|
||||
@@ -216,14 +226,9 @@ export default {
|
||||
this.pagination = pagination;
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
});
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -109,7 +110,7 @@ export default {
|
||||
onShowSizeChange: (current, size) => {
|
||||
store.pageSize = size
|
||||
}
|
||||
}, filters: {},
|
||||
}, filters: {},
|
||||
order: "desc",
|
||||
loading: false,
|
||||
columns,
|
||||
@@ -140,14 +141,9 @@ export default {
|
||||
this.pagination = pagination;
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
});
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@close="closeDrawer"
|
||||
>
|
||||
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
||||
<BasicRule :form="form" :readOnly="formReadOnly"/>
|
||||
<BasicRule :form="form" :readOnly="formReadOnly" flagFormat="domain"/>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-model-item label="Type" :rules="rules.type">
|
||||
@@ -90,6 +90,7 @@
|
||||
</a-drawer>
|
||||
<!-- rule table -->
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -296,7 +297,7 @@ export default {
|
||||
onShowSizeChange: (current, size) => {
|
||||
store.pageSize = size
|
||||
}
|
||||
}, filters: {},
|
||||
}, filters: {},
|
||||
loading: false,
|
||||
columns,
|
||||
colors,
|
||||
@@ -332,14 +333,9 @@ export default {
|
||||
this.pagination = pagination;
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
});
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||
}
|
||||
})
|
||||
},
|
||||
clickSwitch(record, prop) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@close="closeDrawer"
|
||||
>
|
||||
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
||||
<BasicRule :form="form" :readOnly="formReadOnly"/>
|
||||
<BasicRule :form="form" :readOnly="formReadOnly" flagFormat="user or password"/>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-model-item>
|
||||
@@ -28,7 +28,7 @@
|
||||
<a-input
|
||||
v-model="form.pasv_address"
|
||||
style="width: 100%"
|
||||
placeholder="Use external IP by default"
|
||||
placeholder="Use `external_ip:pasv_port` by default"
|
||||
:readOnly="formReadOnly"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
@@ -78,6 +78,7 @@
|
||||
</a-drawer>
|
||||
<!-- rule table -->
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -267,14 +268,9 @@ export default {
|
||||
this.pagination = pagination;
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
});
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||
}
|
||||
})
|
||||
},
|
||||
clickSwitch(record, prop) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@close="closeDrawer"
|
||||
>
|
||||
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
||||
<BasicRule :form="form" :readOnly="formReadOnly"/>
|
||||
<BasicRule :form="form" :readOnly="formReadOnly" flagFormat="raw message"/>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-model-item :rules="rules.response_status_code"
|
||||
@@ -130,6 +130,7 @@
|
||||
</a-drawer>
|
||||
<!-- rule table -->
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -374,14 +375,9 @@ export default {
|
||||
this.pagination = pagination;
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
});
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||
}
|
||||
})
|
||||
},
|
||||
clickSwitch(record, prop) {
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
</a-drawer>
|
||||
<!-- rule table -->
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -300,7 +301,7 @@ export default {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
width: '400px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@close="closeDrawer"
|
||||
>
|
||||
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
||||
<BasicRule :form="form" :readOnly="formReadOnly"/>
|
||||
<BasicRule :form="form" :readOnly="formReadOnly" flagFormat="path"/>
|
||||
</a-form-model>
|
||||
<div
|
||||
:style="{
|
||||
@@ -38,6 +38,7 @@
|
||||
</a-drawer>
|
||||
<!-- rule table -->
|
||||
<a-table
|
||||
style="overflow-x: auto;"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
@@ -193,7 +194,8 @@ export default {
|
||||
onShowSizeChange: (current, size) => {
|
||||
store.pageSize = size
|
||||
}
|
||||
}, filters: {},
|
||||
},
|
||||
filters: {},
|
||||
loading: false,
|
||||
columns,
|
||||
form: {},
|
||||
@@ -225,14 +227,9 @@ export default {
|
||||
this.pagination = pagination;
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: 'Unknown error: ' + e.response.status,
|
||||
style: {
|
||||
width: '100px',
|
||||
marginLeft: `${335 - 600}px`,
|
||||
},
|
||||
duration: 4
|
||||
});
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||
}
|
||||
})
|
||||
},
|
||||
clickSwitch(record, prop) {
|
||||
|
||||
@@ -12,13 +12,13 @@ module.exports = {
|
||||
'/api': {
|
||||
target: 'http://localhost:10000',
|
||||
pathRewrite: {
|
||||
'^/revsuit/api': '/revsuit/api'
|
||||
'^/api': '/revsuit/api'
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
productionSourceMap:false,
|
||||
productionSourceMap: false,
|
||||
runtimeCompiler: true,
|
||||
filenameHashing: false,
|
||||
publicPath: ''
|
||||
publicPath: '/revsuit/admin/',
|
||||
}
|
||||
|
||||
@@ -15,17 +15,17 @@ require (
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.6 // indirect
|
||||
github.com/miekg/dns v1.1.38
|
||||
github.com/oschwald/geoip2-golang v1.5.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sinlov/qqwry-golang v0.0.0-20191204062238-bea9868bbbf4
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/ugorji/go v1.2.4 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||
golang.org/x/sys v0.0.0-20210217105451-b926d437f341 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
gorm.io/driver/sqlite v1.1.4
|
||||
gorm.io/gorm v1.21.4
|
||||
|
||||
@@ -427,6 +427,10 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
|
||||
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -518,8 +522,9 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tchap/go-patricia v0.0.0-20160729071656-dd168db6051b/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
|
||||
github.com/tebeka/selenium v0.9.9/go.mod h1:5Fr8+pUvU6B1OiPfkdCKdXZyr5znvVkxuPd0NOdZCQc=
|
||||
@@ -668,6 +673,7 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 764 KiB |
|
After Width: | Height: | Size: 135 KiB |
@@ -1,15 +1,21 @@
|
||||
version: 1.0
|
||||
version: 1.3
|
||||
addr: :10000
|
||||
token:
|
||||
domain:
|
||||
external_ip: # You need to make sure that your target can access the server through this ip.
|
||||
admin_path_prefix: "/revsuit"
|
||||
database: revsuit.db
|
||||
log_level: info
|
||||
|
||||
ip_location_database:
|
||||
database: "qqwry" # qqwry or geoip.
|
||||
geo_license_key: "" # Mandatory field, if you choose to use GeoIP.
|
||||
|
||||
http:
|
||||
ip_header:
|
||||
dns:
|
||||
enable: true
|
||||
addr: :53
|
||||
rmi:
|
||||
enable: true
|
||||
addr: :1099
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package ipinfo
|
||||
|
||||
type Config struct {
|
||||
Database string
|
||||
GeoLicenseKey string `yaml:"geo_license_key"`
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package ipinfo
|
||||
|
||||
type Database interface {
|
||||
Area(string) string
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
Url = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz"
|
||||
)
|
||||
|
||||
func get(url string) (b []byte, err error) {
|
||||
client := http.Client{
|
||||
Timeout: 90 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // disable verify
|
||||
}}
|
||||
request, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
request.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36")
|
||||
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func extractTarGz(gzipStream io.Reader) error {
|
||||
uncompressedStream, err := gzip.NewReader(gzipStream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tarReader := tar.NewReader(uncompressedStream)
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeReg:
|
||||
log.Trace(header.Name)
|
||||
if !strings.HasSuffix(header.Name, "GeoLite2-City.mmdb") {
|
||||
continue
|
||||
}
|
||||
outFile, err := os.Create("GeoLite2-City.mmdb")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func download(licenseKey string) (err error) {
|
||||
var GeoLite2TarGz []byte
|
||||
log.Info("Downloading GeoLite2-City.mmdb...")
|
||||
if GeoLite2TarGz, err = get(fmt.Sprintf(Url, licenseKey)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return extractTarGz(bytes.NewReader(GeoLite2TarGz))
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
geo *geoip2.Reader
|
||||
}
|
||||
|
||||
var geo *geoip2.Reader
|
||||
var once sync.Once
|
||||
|
||||
// Area returns IpArea according to ip
|
||||
func (db *Database) Area(ip string) string {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
record, err := db.geo.City(net.ParseIP(ip))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
country := record.Country.Names["en"]
|
||||
city := record.City.Names["en"]
|
||||
if city == "" {
|
||||
city = record.Location.TimeZone
|
||||
}
|
||||
return fmt.Sprintf("%s %s", country, city)
|
||||
}
|
||||
|
||||
func checkUpdate(licenseKey string) {
|
||||
info, err := os.Stat("GeoLite2-City.mmdb")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err := download(licenseKey)
|
||||
if err != nil {
|
||||
log.Warn("Download GeoLite2-City.mmdb failed, caused by:%v, recommend to download it by yourself otherwise the `IpArea` will be null", err)
|
||||
}
|
||||
}
|
||||
} else if -time.Until(info.ModTime()) > 7*24*time.Hour {
|
||||
log.Info("Updating GeoLite2-City.mmdb...")
|
||||
err := download(licenseKey)
|
||||
if err != nil {
|
||||
log.Warn("Update GeoLite2-City.mmdb failed, please download GeoLite2-City.mmdb by yourself")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func New(licenseKey string) *Database {
|
||||
once.Do(func() {
|
||||
var err error
|
||||
checkUpdate(licenseKey)
|
||||
geo, err = geoip2.Open("GeoLite2-City.mmdb")
|
||||
if err != nil {
|
||||
log.Error("Load GeoLite2-City.mmdb failed, `IpArea` will be null")
|
||||
}
|
||||
})
|
||||
return &Database{geo: geo}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ipinfo
|
||||
|
||||
import (
|
||||
"github.com/li4n0/revsuit/internal/ipinfo/geoip"
|
||||
"github.com/li4n0/revsuit/internal/ipinfo/qqwry"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
var db Database
|
||||
|
||||
func Area(ip string) string {
|
||||
if db != nil {
|
||||
return db.Area(ip)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Init(config Config) {
|
||||
switch config.Database {
|
||||
case "qqwry":
|
||||
db = qqwry.New()
|
||||
case "geoip":
|
||||
db = geoip.New(config.GeoLicenseKey)
|
||||
default:
|
||||
log.Fatal("wrong ip location database type: %q", config.Database)
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,9 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
|
||||
const (
|
||||
CopyWriteUrl = "https://qqwry.mirror.noc.one/copywrite.rar"
|
||||
QqwryUrl = "https://qqwry.mirror.noc.one/qqwry.rar"
|
||||
Url = "https://qqwry.mirror.noc.one/qqwry.rar"
|
||||
)
|
||||
|
||||
func get(url string) (b []byte, err error) {
|
||||
@@ -34,8 +35,7 @@ func get(url string) (b []byte, err error) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err = ioutil.ReadAll(resp.Body)
|
||||
return b, err
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func getKey(b []byte) (key uint32, err error) {
|
||||
@@ -58,15 +58,17 @@ func decrypt(b []byte, key uint32) (_ []byte, err error) {
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
return ioutil.ReadAll(rc)
|
||||
return io.ReadAll(rc)
|
||||
}
|
||||
|
||||
func download() (err error) {
|
||||
var (
|
||||
copyWriteData, qqwryData []byte
|
||||
wg sync.WaitGroup
|
||||
key uint32
|
||||
)
|
||||
log.Info("Downloading qqwry.dat...")
|
||||
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@@ -77,23 +79,21 @@ func download() (err error) {
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if qqwryData, err = get(QqwryUrl); err != nil {
|
||||
if qqwryData, err = get(Url); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var key uint32
|
||||
if key, err = getKey(copyWriteData); err != nil {
|
||||
return
|
||||
}
|
||||
b, err := decrypt(qqwryData, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = ioutil.WriteFile("qqwry.dat", b, 0644)
|
||||
|
||||
return nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if key, err = getKey(copyWriteData); err != nil {
|
||||
return err
|
||||
}
|
||||
if b, err := decrypt(qqwryData, key); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return os.WriteFile("qqwry.dat", b, 0644)
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,29 @@ import (
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
wry *qqwry.QQwry
|
||||
}
|
||||
|
||||
// Area returns IpArea according to ip
|
||||
func (db *Database) Area(ip string) string {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
if db.wry == nil {
|
||||
return ""
|
||||
}
|
||||
ipData := db.wry.SearchByIPv4(ip)
|
||||
if ipData.Area == " CZ88.NET" {
|
||||
return ipData.Country
|
||||
}
|
||||
return ipData.Country + " " + ipData.Area
|
||||
}
|
||||
|
||||
var wry *qqwry.QQwry
|
||||
var once sync.Once
|
||||
|
||||
func init() {
|
||||
_ = log.NewConsole(100,
|
||||
log.ConsoleConfig{
|
||||
Level: log.LevelInfo,
|
||||
})
|
||||
func checkUpdate() {
|
||||
info, err := os.Stat("qqwry.dat")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -25,7 +40,7 @@ func init() {
|
||||
log.Warn("Download qqwry.dat failed, caused by:%v, recommend to download it by yourself otherwise the `IpArea` will be null", err)
|
||||
}
|
||||
}
|
||||
} else if -time.Until(info.ModTime()) > 5*24*time.Hour {
|
||||
} else if -time.Until(info.ModTime()) > 7*24*time.Hour {
|
||||
log.Info("Updating qqwry.dat...")
|
||||
err := download()
|
||||
if err != nil {
|
||||
@@ -34,8 +49,9 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func GetQQWry() *qqwry.QQwry {
|
||||
func New() *Database {
|
||||
once.Do(func() {
|
||||
checkUpdate()
|
||||
qqwry.DatData.FilePath = "qqwry.dat"
|
||||
init := qqwry.DatData.InitDatFile()
|
||||
if v, ok := init.(error); ok {
|
||||
@@ -46,6 +62,5 @@ func GetQQWry() *qqwry.QQwry {
|
||||
}
|
||||
wry = qqwry.NewQQwry()
|
||||
})
|
||||
|
||||
return wry
|
||||
return &Database{wry: wry}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- "1.12"
|
||||
- tip
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
install:
|
||||
- go get -t ./...
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||
@@ -1,10 +0,0 @@
|
||||
all: fmt vet lint
|
||||
|
||||
fmt:
|
||||
go fmt .
|
||||
|
||||
vet:
|
||||
go vet .
|
||||
|
||||
lint:
|
||||
golint .
|
||||
@@ -1,16 +0,0 @@
|
||||
package qqwry
|
||||
|
||||
// Area returns IpArea according to ip
|
||||
func Area(ip string) string {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
if GetQQWry() == nil {
|
||||
return ""
|
||||
}
|
||||
ipData := GetQQWry().SearchByIPv4(ip)
|
||||
if ipData.Area == " CZ88.NET" {
|
||||
return ipData.Country
|
||||
}
|
||||
return ipData.Country + " " + ipData.Area
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var recordChan = make(chan BaseRecord, 100)
|
||||
var recordChan = make(chan BaseRecord, 1000)
|
||||
|
||||
type Record interface {
|
||||
GetFlag() string
|
||||
|
||||
@@ -2,4 +2,5 @@ package dns
|
||||
|
||||
type Config struct {
|
||||
Enable bool
|
||||
Addr string
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/li4n0/revsuit/internal/database"
|
||||
"github.com/li4n0/revsuit/internal/ipinfo"
|
||||
"github.com/li4n0/revsuit/internal/newdns"
|
||||
"github.com/li4n0/revsuit/internal/qqwry"
|
||||
"github.com/li4n0/revsuit/internal/recycler"
|
||||
"github.com/li4n0/revsuit/internal/rule"
|
||||
"github.com/patrickmn/go-cache"
|
||||
@@ -53,7 +53,7 @@ func (s *Server) getRules() []*Rule {
|
||||
return s.rules
|
||||
}
|
||||
|
||||
func (s *Server) updateRules() error {
|
||||
func (s *Server) UpdateRules() error {
|
||||
db := database.DB.Model(new(Rule))
|
||||
defer s.rulesLock.Unlock()
|
||||
s.rulesLock.Lock()
|
||||
@@ -135,7 +135,7 @@ func (s *Server) newZone(name string) *newdns.Zone {
|
||||
continue
|
||||
}
|
||||
|
||||
r, err := newRecord(_rule, flag, domain, ip, qqwry.Area(ip))
|
||||
r, err := newRecord(_rule, flag, domain, ip, ipinfo.Area(ip))
|
||||
if err != nil {
|
||||
log.Warn("DNS record(rule_id:%s) created failed :%s", _rule.Name, err)
|
||||
return nil, nil
|
||||
@@ -209,7 +209,7 @@ func (s *Server) Run() {
|
||||
s.livingLock.Unlock()
|
||||
}()
|
||||
|
||||
if err := s.updateRules(); err != nil {
|
||||
if err := s.UpdateRules(); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
@@ -252,7 +252,7 @@ func (s *Server) Run() {
|
||||
}
|
||||
}()
|
||||
|
||||
err := server.Run(":53")
|
||||
err := server.Run(s.Addr)
|
||||
defer server.Close()
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -47,16 +47,17 @@ func ListRecords(c *gin.Context) {
|
||||
res []Record
|
||||
count int64
|
||||
order = c.Query("order")
|
||||
pageSize int
|
||||
pageSize = 10
|
||||
)
|
||||
|
||||
if c.Query("pageSize") == "" {
|
||||
pageSize = 10
|
||||
} else if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n <= 0 || n > 100 {
|
||||
pageSize = 10
|
||||
if c.Query("pageSize") != "" {
|
||||
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n > 0 && n < 100 {
|
||||
pageSize = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.ShouldBind(&dnsRecord); err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"status": "failed",
|
||||
|
||||
@@ -58,7 +58,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (r *Rule) Delete() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -80,14 +80,14 @@ func ListRules(c *gin.Context) {
|
||||
res []Rule
|
||||
count int64
|
||||
order = c.Query("order")
|
||||
pageSize int
|
||||
pageSize = 10
|
||||
)
|
||||
|
||||
if c.Query("pageSize") == "" {
|
||||
pageSize = 10
|
||||
} else if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n <= 0 || n > 100 {
|
||||
pageSize = 10
|
||||
if c.Query("pageSize") != "" {
|
||||
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n > 0 && n < 100 {
|
||||
pageSize = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ func (s *Server) getRules() []*Rule {
|
||||
return s.rules
|
||||
}
|
||||
|
||||
func (s *Server) updateRules() error {
|
||||
func (s *Server) UpdateRules() error {
|
||||
db := database.DB.Model(new(Rule))
|
||||
defer s.rulesLock.Unlock()
|
||||
s.rulesLock.Lock()
|
||||
@@ -94,6 +94,7 @@ func (s *Server) authenticate(user, password string) (_rule *Rule, flag, flagGro
|
||||
}
|
||||
|
||||
func (s *Server) getPasvAddressFromCache(ip, pasvAddressTpl string) (pasvAddress string) {
|
||||
pasvAddress = pasvAddressTpl
|
||||
if strings.Contains(pasvAddressTpl, ",") {
|
||||
values, ok := rebindingCache.Get(ip)
|
||||
if !ok {
|
||||
@@ -202,8 +203,8 @@ loop:
|
||||
if pasvAddress = s.getPasvAddressFromCache(ip, _rule.PasvAddress); pasvAddress == "" {
|
||||
pasvAddress = fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
||||
}
|
||||
|
||||
isRedirect = rule.CompileTpl(pasvAddress, vars) != fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
||||
pasvAddress = rule.CompileTpl(pasvAddress, vars)
|
||||
isRedirect = pasvAddress != fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
||||
case "SIZE":
|
||||
path += strings.TrimLeft(args, "/")
|
||||
if _rule == nil || isRedirect || len(_rule.Data) == 0 {
|
||||
@@ -217,8 +218,6 @@ loop:
|
||||
case "PASV":
|
||||
//Just so that ide does not prompt that there may be a nil value
|
||||
if _rule != nil {
|
||||
// return rule's pasv_address or default pasv address
|
||||
pasvAddress := rule.CompileTpl(pasvAddress, vars)
|
||||
pasvIP, pasvPort, err := net.SplitHostPort(pasvAddress)
|
||||
if err != nil {
|
||||
log.Warn("FTP failed to split rule[id:%d] pasv_address(%s) :%s", _rule.ID, pasvAddress, err)
|
||||
@@ -365,7 +364,7 @@ func (s *Server) Run() {
|
||||
s.livingLock.Unlock()
|
||||
}()
|
||||
|
||||
if err := s.updateRules(); err != nil {
|
||||
if err := s.UpdateRules(); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/li4n0/revsuit/internal/database"
|
||||
"github.com/li4n0/revsuit/internal/file"
|
||||
"github.com/li4n0/revsuit/internal/ipinfo"
|
||||
"github.com/li4n0/revsuit/internal/notice"
|
||||
"github.com/li4n0/revsuit/internal/qqwry"
|
||||
"github.com/li4n0/revsuit/internal/record"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
@@ -59,14 +59,14 @@ func ListRecords(c *gin.Context) {
|
||||
res []Record
|
||||
count int64
|
||||
order = c.Query("order")
|
||||
pageSize int
|
||||
pageSize = 10
|
||||
)
|
||||
|
||||
if c.Query("pageSize") == "" {
|
||||
pageSize = 10
|
||||
} else if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n <= 0 || n > 100 {
|
||||
pageSize = 10
|
||||
if c.Query("pageSize") != "" {
|
||||
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n > 0 && n < 100 {
|
||||
pageSize = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ func ListRecords(c *gin.Context) {
|
||||
|
||||
func createRecord(_rule *Rule, flag, flagGroup, user, password, method, path, filename, ip string, uploadData []byte, status Status) {
|
||||
// create new record
|
||||
area := qqwry.Area(ip)
|
||||
area := ipinfo.Area(ip)
|
||||
var ftpFile *file.FTPFile
|
||||
var r *Record
|
||||
var err error
|
||||
|
||||
@@ -54,7 +54,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
return GetServer().updateRules()
|
||||
return GetServer().UpdateRules()
|
||||
}
|
||||
|
||||
// Delete deletes the ftp rule in database and ruleSet
|
||||
@@ -65,7 +65,7 @@ func (r *Rule) Delete() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
return GetServer().updateRules()
|
||||
return GetServer().UpdateRules()
|
||||
}
|
||||
|
||||
// ListRules lists all ftp rules those satisfy the filter
|
||||
@@ -75,14 +75,14 @@ func ListRules(c *gin.Context) {
|
||||
res []Rule
|
||||
count int64
|
||||
order = c.Query("order")
|
||||
pageSize int
|
||||
pageSize = 10
|
||||
)
|
||||
|
||||
if c.Query("pageSize") == "" {
|
||||
pageSize = 10
|
||||
} else if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n <= 0 || n > 100 {
|
||||
pageSize = 10
|
||||
if c.Query("pageSize") != "" {
|
||||
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n > 0 && n < 100 {
|
||||
pageSize = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/li4n0/revsuit/internal/database"
|
||||
"github.com/li4n0/revsuit/internal/file"
|
||||
"github.com/li4n0/revsuit/internal/qqwry"
|
||||
"github.com/li4n0/revsuit/internal/ipinfo"
|
||||
"github.com/li4n0/revsuit/pkg/mysql/vmysql"
|
||||
"github.com/pkg/errors"
|
||||
log "unknwon.dev/clog/v2"
|
||||
@@ -48,7 +48,7 @@ func (s *Server) getRules() []*Rule {
|
||||
return s.rules
|
||||
}
|
||||
|
||||
func (s *Server) updateRules() error {
|
||||
func (s *Server) UpdateRules() error {
|
||||
db := database.DB.Model(new(Rule))
|
||||
defer s.rulesLock.Unlock()
|
||||
s.rulesLock.Lock()
|
||||
@@ -105,11 +105,11 @@ func (s *Server) ConnectionClosed(c *vmysql.Conn) {
|
||||
var clientName, clientOS, flag, flagGroup string
|
||||
|
||||
user := c.User
|
||||
schema := c.SchemaName
|
||||
supportLoadLocalData := c.SupportLoadDataLocal
|
||||
|
||||
cr, ok := s.connRulePool.Load(c.ConnectionID)
|
||||
if !ok {
|
||||
log.Warn("MySQL Connection rule(%d) not match flag", c.ConnectionID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func (s *Server) ConnectionClosed(c *vmysql.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
r, err := newRecord(_rule, flag, user, clientName, clientOS, ip, qqwry.Area(ip), supportLoadLocalData, files)
|
||||
r, err := newRecord(_rule, flag, user, schema, clientName, clientOS, ip, ipinfo.Area(ip), supportLoadLocalData, files)
|
||||
if err != nil {
|
||||
log.Warn("MySQL record[rule_id: %s] created failed: %s", _rule.Name, err)
|
||||
return
|
||||
@@ -288,7 +288,7 @@ func (s *Server) Run() {
|
||||
s.Enable = false
|
||||
s.livingLock.Unlock()
|
||||
}()
|
||||
if err := s.updateRules(); err != nil {
|
||||
if err := s.UpdateRules(); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type Record struct {
|
||||
record.BaseRecord
|
||||
Username string `gorm:"index" form:"username" json:"username" notice:"username"`
|
||||
ClientName string `gorm:"index" form:"client_name" json:"client_name" notice:"client_name"`
|
||||
Schema string `gorm:"index" form:"schema" json:"schema" notice:"schema"`
|
||||
ClientOS string `gorm:"index" form:"client_os" json:"client_os" notice:"client_os"`
|
||||
LoadLocalData bool `gorm:"index" form:"load_local_data" json:"load_local_data" notice:"load_local_data"`
|
||||
Files []file.MySQLFile `form:"-" json:"files" notice:"-"`
|
||||
@@ -31,7 +32,7 @@ func (r Record) Notice() {
|
||||
notice.Notice(r)
|
||||
}
|
||||
|
||||
func newRecord(rule *Rule, flag, username, clientName, clientOS, remoteIp, ipArea string, supportLoadLocalData bool, files []file.MySQLFile) (r *Record, err error) {
|
||||
func newRecord(rule *Rule, flag, username, schema, clientName, clientOS, remoteIp, ipArea string, supportLoadLocalData bool, files []file.MySQLFile) (r *Record, err error) {
|
||||
r = &Record{
|
||||
BaseRecord: record.BaseRecord{
|
||||
Flag: flag,
|
||||
@@ -40,6 +41,7 @@ func newRecord(rule *Rule, flag, username, clientName, clientOS, remoteIp, ipAre
|
||||
RequestTime: time.Now(),
|
||||
},
|
||||
Username: username,
|
||||
Schema: schema,
|
||||
ClientName: clientName,
|
||||
ClientOS: clientOS,
|
||||
LoadLocalData: supportLoadLocalData,
|
||||
@@ -55,14 +57,14 @@ func ListRecords(c *gin.Context) {
|
||||
res []Record
|
||||
count int64
|
||||
order = c.Query("order")
|
||||
pageSize int
|
||||
pageSize = 10
|
||||
)
|
||||
|
||||
if c.Query("pageSize") == "" {
|
||||
pageSize = 10
|
||||
} else if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n <= 0 || n > 100 {
|
||||
pageSize = 10
|
||||
if c.Query("pageSize") != "" {
|
||||
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n > 0 && n < 100 {
|
||||
pageSize = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +92,9 @@ func ListRecords(c *gin.Context) {
|
||||
if mysqlRecord.ClientName != "" {
|
||||
db.Where("client_name like ?", "%"+mysqlRecord.ClientName)
|
||||
}
|
||||
if mysqlRecord.Schema != "" {
|
||||
db.Where("schema like ?", "%"+mysqlRecord.Schema)
|
||||
}
|
||||
if c.Query("load_local_data") != "" {
|
||||
if c.Query("load_local_data") == "true" {
|
||||
db.Where("load_local_data = ?", true)
|
||||
|
||||
@@ -41,7 +41,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (r *Rule) Delete() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -63,14 +63,14 @@ func ListRules(c *gin.Context) {
|
||||
res []Rule
|
||||
count int64
|
||||
order = c.Query("order")
|
||||
pageSize int
|
||||
pageSize = 10
|
||||
)
|
||||
|
||||
if c.Query("pageSize") == "" {
|
||||
pageSize = 10
|
||||
} else if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n <= 0 || n > 100 {
|
||||
pageSize = 10
|
||||
if c.Query("pageSize") != "" {
|
||||
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n > 0 && n < 100 {
|
||||
pageSize = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package rhttp
|
||||
|
||||
type Config struct {
|
||||
IpHeader string
|
||||
IpHeader string `yaml:"ip_header"`
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/li4n0/revsuit/internal/database"
|
||||
"github.com/li4n0/revsuit/internal/qqwry"
|
||||
"github.com/li4n0/revsuit/internal/ipinfo"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
@@ -83,7 +83,7 @@ func (s *Server) getRules() []*Rule {
|
||||
return s.rules
|
||||
}
|
||||
|
||||
func (s *Server) updateRules() error {
|
||||
func (s *Server) UpdateRules() error {
|
||||
db := database.DB.Model(new(Rule))
|
||||
defer s.rulesLock.Unlock()
|
||||
s.rulesLock.Lock()
|
||||
@@ -116,7 +116,7 @@ func (s *Server) Restart() {
|
||||
}
|
||||
|
||||
func (s *Server) Run() {
|
||||
if err := s.updateRules(); err != nil {
|
||||
if err := s.UpdateRules(); err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
for {
|
||||
@@ -156,29 +156,25 @@ func compileTpl(c *gin.Context, tpl string, vars map[string]string) (compiled st
|
||||
|
||||
func (s *Server) Receive(c *gin.Context) {
|
||||
u := c.Request.URL.String()
|
||||
raw, err := getRawRequest(c.Request)
|
||||
if err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
for _, _rule := range s.getRules() {
|
||||
flag, flagGroup, vars := _rule.Match(u)
|
||||
flag, flagGroup, vars := _rule.Match(string(raw))
|
||||
if flag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
ip = strings.Split(c.Request.RemoteAddr, ":")[0]
|
||||
area = qqwry.Area(ip)
|
||||
)
|
||||
ip := strings.Split(c.Request.RemoteAddr, ":")[0]
|
||||
|
||||
if ip1 := c.Request.Header.Get(s.IpHeader); s.IpHeader != "" && ip1 != "" {
|
||||
ip = ip1
|
||||
delete(c.Request.Header, s.IpHeader)
|
||||
}
|
||||
|
||||
raw, err := getRawRequest(c.Request)
|
||||
if err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
|
||||
// create new record
|
||||
r, err := NewRecord(_rule, flag, c.Request.Method, u, ip, area, string(raw))
|
||||
r, err := NewRecord(_rule, flag, c.Request.Method, u, ip, ipinfo.Area(ip), string(raw))
|
||||
if err != nil {
|
||||
log.Warn("HTTP record[rule_id:%d] created failed :%s", _rule.ID, err)
|
||||
code, err := strconv.Atoi(compileTpl(c, _rule.ResponseStatusCode, vars))
|
||||
|
||||
@@ -14,7 +14,7 @@ var _ record.Record = (*Record)(nil)
|
||||
|
||||
type Record struct {
|
||||
Method string `gorm:"index" form:"method" json:"method"`
|
||||
Path string `form:"path" json:"path"`
|
||||
URI string `form:"uri" json:"uri"`
|
||||
record.BaseRecord
|
||||
RawRequest string `json:"raw_request" notice:"-"`
|
||||
Rule Rule `gorm:"foreignKey:RuleName;references:Name;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" form:"-" json:"-" notice:"-"`
|
||||
@@ -37,7 +37,7 @@ func NewRecord(rule *Rule, flag, method, url, ip, area, raw string) (r *Record,
|
||||
RequestTime: time.Now(),
|
||||
},
|
||||
Method: method,
|
||||
Path: url,
|
||||
URI: url,
|
||||
RawRequest: raw,
|
||||
Rule: *rule,
|
||||
}
|
||||
@@ -50,15 +50,14 @@ func ListRecords(c *gin.Context) {
|
||||
res []Record
|
||||
count int64
|
||||
order = c.Query("order")
|
||||
pageSize int
|
||||
pageSize = 10
|
||||
)
|
||||
|
||||
if c.Query("pageSize") == "" {
|
||||
pageSize = 10
|
||||
} else if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
pageSize = n
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
if c.Query("pageSize") != "" {
|
||||
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n > 0 && n < 100 {
|
||||
pageSize = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +77,8 @@ func ListRecords(c *gin.Context) {
|
||||
if httpRecord.Method != "" {
|
||||
db.Where("method = ?", httpRecord.Method)
|
||||
}
|
||||
if httpRecord.Path != "" {
|
||||
db.Where("path like ?", "%"+httpRecord.Path+"%")
|
||||
if httpRecord.URI != "" {
|
||||
db.Where("path like ?", "%"+httpRecord.URI+"%")
|
||||
}
|
||||
if httpRecord.RemoteIP != "" {
|
||||
db.Where("remote_ip = ?", httpRecord.RemoteIP)
|
||||
|
||||
@@ -58,7 +58,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (r *Rule) Delete() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -81,14 +81,14 @@ func ListRules(c *gin.Context) {
|
||||
res []Rule
|
||||
count int64
|
||||
order = c.Query("order")
|
||||
pageSize int
|
||||
pageSize = 10
|
||||
)
|
||||
|
||||
if c.Query("pageSize") == "" {
|
||||
pageSize = 10
|
||||
} else if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n <= 0 || n > 100 {
|
||||
pageSize = 10
|
||||
if c.Query("pageSize") != "" {
|
||||
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n > 0 && n < 100 {
|
||||
pageSize = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,14 +46,14 @@ func ListRecords(c *gin.Context) {
|
||||
res []Record
|
||||
count int64
|
||||
order = c.Query("order")
|
||||
pageSize int
|
||||
pageSize = 10
|
||||
)
|
||||
|
||||
if c.Query("pageSize") == "" {
|
||||
pageSize = 10
|
||||
} else if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n <= 0 || n > 100 {
|
||||
pageSize = 10
|
||||
if c.Query("pageSize") != "" {
|
||||
if n, err := strconv.Atoi(c.Query("pageSize")); err == nil {
|
||||
if n > 0 && n < 100 {
|
||||
pageSize = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||