Merge pull request #4 from bmartin5692/dev
Beginning bumper's new journey
This commit is contained in:
commit
95b4b05633
32 changed files with 2910 additions and 360 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@ __pycache__
|
||||||
Pipfile.lock
|
Pipfile.lock
|
||||||
/bumper.sublime-project
|
/bumper.sublime-project
|
||||||
/bumper.sublime-workspace
|
/bumper.sublime-workspace
|
||||||
|
.vscode/
|
||||||
8
Pipfile
8
Pipfile
|
|
@ -1,13 +1,11 @@
|
||||||
[[source]]
|
[[source]]
|
||||||
|
|
||||||
url = "https://pypi.python.org/simple"
|
url = "https://pypi.python.org/simple"
|
||||||
verify_ssl = true
|
verify_ssl = true
|
||||||
name = "pypi"
|
name = "pypi"
|
||||||
|
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
|
hbmqtt = "*"
|
||||||
|
aiohttp = "*"
|
||||||
|
black = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
|
|
||||||
214
README.md
214
README.md
|
|
@ -1,109 +1,201 @@
|
||||||
Bumper
|
|
||||||
=====
|
|
||||||
|
|
||||||
A standalone implementation of the central server used by Ecovacs
|
|
||||||
Deebot cleaning robots to relay data between the robot and client.
|
|
||||||
|
|
||||||
Tested on Ecovacs Deebot M81 Pro but should be compatible with
|
# Bumper
|
||||||
most wifi-enabled robots that use the Ecovacs Android app.
|
A standalone implementation of the central server used by Ecovacs Deebot cleaning robots to relay data between the robot and client.
|
||||||
|
|
||||||
*Please note: this software is experimental and not ready
|
A big thanks to the original project creator @torbjornaxelsson, without his work this project would have taken much longer to build. The project reached its original goal and remained in a stable, but stale state with the last commit in Dec 2017. Since the original release of bumper newer bots have been released that use different protocols, and a growing Ecovacs user base has sparked further interest in the project.
|
||||||
for production use. Use at your own risk.*
|
|
||||||
|
|
||||||
|
In early 2019 after making massive changes to the codebase and with ideas for additional feature development, it was decided that @bmartin5692 would take over active development. The fork was detached and all future development of bumper will take place here.
|
||||||
|
|
||||||
|
The current master branch is unstable and based on the work for adding D900 series support to bumper, along with other code changes. Additional testing is needed with bots that use the XMPP protocols to ensure compatibility, so at this time it can only be guaranteed for 900-series bots (900/901/907).
|
||||||
|
|
||||||
|
If you want a working version of bumper for XMPP bots (M81 Pro, and others), the original code base has been branched off as [v0.1.0](https://github.com/bmartin5692/bumper/tree/v0.1.0
|
||||||
|
) and will remain in the original state. Bumper needs users of models that use XMPP to assist with development efforts in order to ensure compatability as bumper moves forward.
|
||||||
|
|
||||||
|
*Please note: this software is experimental and not ready for production use. Use at your own risk.*
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
As work to reverse the protocols and provide a self-hosted central server is still in progress, Bumper has had limited testing. There are a number of EcoVacs models and varying protocols that it hasn't been tested against. So far two different protocols have been seen, **XMPP** and **MQTT**. Bumper should be compatible with most wifi-enabled robots that use the Ecovacs Android app, but has only been reported to work on the below:
|
||||||
|
|
||||||
|
| Model | Protocol Used | Bumper Version |
|
||||||
|
|--|--|--|
|
||||||
|
| Deebot 900/901 | MQTT | master |
|
||||||
|
| Deebot M81 Pro | XMPP | v0.1.0 |
|
||||||
|
|
||||||
|
For more information about the protocols and how it works, see the [How does it work?](#how-does-it-work) section at the end. If you test against another model and it works, please report it so it can be added to the list.
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
For fun, mostly :)
|
For fun, mostly :)
|
||||||
|
|
||||||
But seriously, there are a serveral reasons for
|
But seriously, there are a several reasons for eliminating the central server:
|
||||||
elminating the central server:
|
|
||||||
|
|
||||||
1. Convenience: It works without an internet connection or if
|
|
||||||
Ecovacs servers are down
|
|
||||||
2. Performance: No need for messages to travel to Ecovacs server
|
|
||||||
and back.
|
|
||||||
3. Security: We can completely isolate the robot from the public
|
|
||||||
Internet.
|
|
||||||
|
|
||||||
|
1. Convenience: It works without an internet connection or if Ecovacs servers are down
|
||||||
|
2. Performance: No need for messages to travel to Ecovacs server and back.
|
||||||
|
3. Security: We can completely isolate the robot from the public Internet.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- An Ecovacs wifi-enabled robot
|
- An Ecovacs wifi-enabled robot
|
||||||
- A computer on your local network to run the Bumper server
|
- A computer on your local network to run the Bumper server
|
||||||
- Python 3 and pipenv
|
- Python 3 and pipenv
|
||||||
- A network router that has functionality for overriding DNS queries
|
- A network router that has functionality for overriding DNS queries
|
||||||
- A client that can connect to Bumper and talk to the robot over the
|
- A client that can connect to Bumper and talk to the robot over the Ecovacs protocol.
|
||||||
Ecovacs protocol.
|
- The Android or iOS apps can be used if configured properly. See [Using with the official Android/iOS App](#using-with-the-official-androidios-app) below.
|
||||||
I recomend [Sucks](https://github.com/wpietri/sucks).
|
- [Sucks](https://github.com/wpietri/sucks) can also be used, which can act as a client and control the robots via command-line.
|
||||||
It can run on the same computer and requires only minimal modification
|
|
||||||
to work with Bumper.
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- Run `pipenv install` to install dependencies
|
- Run `pipenv install` to install dependencies
|
||||||
|
- Configure your Ecovacs vacuum using the official mobile app (if you haven't done this already)
|
||||||
|
### DNS
|
||||||
|
You need to configure your router to point DNS locally to where Bumper is running.
|
||||||
|
The easiest way is overriding the main domains used by EcoVacs using DNSMasq/PiHole, by adding address entries in a custom config:
|
||||||
|
|
||||||
- Configure your Ecovacs vacuum using the official mobile app
|
address=/ecouser.net/{bumper server ip}
|
||||||
(if you haven't done this already)
|
address=/ecovacs.com/{bumper server ip}
|
||||||
|
address=/ecovacs.net/{bumper server ip}
|
||||||
|
|
||||||
- Configure your router DNS to point the domain lbo.ecovacs.net to
|
If this isn't an option, you'll need to configure your router DNS to point a number of domains used by the app/robot to the Bumper server.
|
||||||
the machine that will run the Bumper server
|
**Note:** Depending on country, your phone may be using a different domain. Most of these domains contain country-specific placeholders.
|
||||||
|
- Example: If you see `eco-{countrycode}-api.ecovacs.com` and you live in the US/North America you would use: `eco-us-api.ecovacs.com`
|
||||||
|
|
||||||
|
| Address | Description |
|
||||||
|
|--|--|
|
||||||
|
| `lb-{countrycode}.ecovacs.net` | Load-balancer that is checked by the app/robot |
|
||||||
|
| `eco-{countrycode}-api.ecovacs.com` | Used for Login |
|
||||||
|
| `portal-{countrycode}.ecouser.net` | Used for Login and Rest API |
|
||||||
|
| `msg-{countrycode}.ecouser.net` | Used for XMPP |
|
||||||
|
| `mq-ww.ecouser.net` | Used for MQTT |
|
||||||
|
|
||||||
|
### Starting Bumper
|
||||||
- Start Bumper with `pipenv run python bumper.py`
|
- Start Bumper with `pipenv run python bumper.py`
|
||||||
|
|
||||||
- Reboot your robot (remove and re-insert the battery pack,
|
- Reboot your robot
|
||||||
then power it on)
|
- **Note:** Some models may require removing and re-inserting the battery pack.
|
||||||
|
- This doesn't seem to be required for models that don't have easily accessible batteries such as the 900/901.
|
||||||
|
|
||||||
- If your configuration is correct, the robot will connect to Bumper
|
- If your configuration is correct, the robot will connect to Bumper within about 30 seconds. Bumper will output information about the connection status.
|
||||||
within about 30 seconds. Bumper will output informaiton about the
|
|
||||||
connection status.
|
|
||||||
|
|
||||||
- Configure your client - see below.
|
- Configure your client - see below.
|
||||||
|
|
||||||
|
|
||||||
## Using with Sucks
|
## Using with Sucks
|
||||||
|
|
||||||
Instructions (verified to work with Sucks 0.8.3)
|
Instructions (verified to work with Sucks 0.8.3)
|
||||||
|
|
||||||
- Download and install [Sucks](https://github.com/wpietri/sucks)
|
- Download and install [Sucks](https://github.com/wpietri/sucks)
|
||||||
- See the [example script](examples/sucks.py) for how to connect
|
- See the [example script](examples/sucks.py) for how to connect
|
||||||
|
|
||||||
|
|
||||||
## Using with the official Android/iOS App
|
## Using with the official Android/iOS App
|
||||||
|
Bumper *can* be used with the official app, but with limitations. Your phone needs to use your DNS server with custom settings, and you ***must*** import Bumper's CA cert and trust it before the app will work.
|
||||||
|
### DNS
|
||||||
|
- Configure your DNS server as described above in the [DNS](#dns) section.
|
||||||
|
### Import the Bumper CA Cert
|
||||||
|
- E-mail yourself the Bumper CA cert (located at `./certs/CA/cacert.crt`)
|
||||||
|
**Note:** Make sure you select the `cacert.crt` file, which is a DER encoded version that will work on either Android or iOS.
|
||||||
|
|
||||||
Bumper *can* be used with the official app, but with limitations. Your
|

|
||||||
phone needs to use your DNS server with custom settings, and the app
|
|
||||||
authenticates via Ecovacs central servers every time you start it.
|
|
||||||
|
|
||||||
- Configure your DNS server to point the domains msg-na.ecouser.net and
|
- Import the cert as a CA, and trust it
|
||||||
msg-ww.ecouser.net to the machine running Bumpy. Note: Depending on
|
- Instructions here are different for [iOS](#importing-the-ca-cert-on-ios) vs [Android](#importing-the-ca-cert-on-android)
|
||||||
country, your phone may be using a different domain.
|
#### Importing the CA Cert on iOS
|
||||||
|
|
||||||
- Login to the app. It will authenticate and ask for a list of robots
|
1. Open the e-mail on your iOS device, and click the attached cert
|
||||||
from Ecovacs central servers.
|
|
||||||
|
|
||||||
- The app will now connect to Bumper and try to ping the robot. Bumper
|

|
||||||
responds to this ping to tell the app that the robot is online.
|
|
||||||
|
|
||||||
- You should now be able to control the robot from the app.
|
2. Install the profile by clicking "Install", and entering your pass code if prompted
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. Accept the certificate warning by clicking "Install" again
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. Click "Done" to exit the profile installation
|
||||||
|
5. Go to Settings > General > About
|
||||||
|
6. Scroll to the bottom and click "Certificate Trust Settings"
|
||||||
|
7. Enable Full Trust for the Bumper CA Cert, by moving the slider to the right
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
8. Click continue when prompted
|
||||||
|
9. That's it, you can now [Use the app](#use-the-app)
|
||||||
|
|
||||||
|
#### Importing the CA Cert on Android
|
||||||
|
|
||||||
|
1. Open the e-mail on your Android device
|
||||||
|
|
||||||
|
**Quick Method**
|
||||||
|
|
||||||
|
2. Click the cert, and if prompted provide a name
|
||||||
|
3. Under "Used for", select "VPN and apps"
|
||||||
|
|
||||||
|
**Long Method**
|
||||||
|
|
||||||
|
2. Save the attached cert file
|
||||||
|
3. Go to Settings > Lock screen and security > Other security settings
|
||||||
|
4. Under "Credential storage", click "Install from device storage"
|
||||||
|
5. Browse to the downloaded cert, select it, then click "Done"
|
||||||
|
6. Click the cert, and if prompted provide a name
|
||||||
|
7. Under "Used for", select "VPN and apps"
|
||||||
|
|
||||||
|
Now, start [using the app](#use-the-app).
|
||||||
|
|
||||||
|
### Use the app
|
||||||
|
|
||||||
|
- Open the app
|
||||||
|
- At this time there is no authentication layer, you can enter any e-mail address and password (as long as it is 6 characters) and you will be authenticated
|
||||||
|
- If your robot has already checked into Bumper, then it will be available in the list of robots
|
||||||
|
- The app now does a ping to the robot to make sure it is online, and if it is you can now control the robot
|
||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
**App/Authentication**
|
||||||
|
|
||||||
Ecovacs robots communicate over the XMPP (jabber) protocol. Messages
|
EcoVacs servers provide authentication of accounts and match those up to registered robots. Once authenticated, users can control the robots via the app. ***Bumper*** provides an implementation of the central servers providing authentication and matching.
|
||||||
are relayed by a central XMPP server.
|
|
||||||
|
|
||||||
Bumper exposes a simulated XMPP server that implements the nessecary
|
**Robots**
|
||||||
functions for relaying messages between a robot and a client.
|
|
||||||
|
|
||||||
When the robot boots up it sends a HTTP request to lbo.ecovacs.net:8007
|
So far two protocols have been identified that various models of EcoVacs robots use: **XMPP** and **MQTT**. These appear to be mutually exclusive and your robot model will use one of these two protocols for communication. ***Bumper*** provides an implementation of both protocols handling communication between the app and robots.
|
||||||
asking for the IP address and port of the XMPP server. Because of our
|
|
||||||
DNS override, this request will be received by Bumper. We tell the robot
|
|
||||||
to connect over XMPP to our local machine. Voilá!
|
|
||||||
|
|
||||||
|
----
|
||||||
|
### Login/Authentication/RestAPI
|
||||||
|
|
||||||
|
***Bumper*** provides a fully simulated central server that handles login/authentication for the app/clients.
|
||||||
|
|
||||||
|
The EcoVacs app encrypts the username/password with the public key of EcoVacs when authenticating. Since we don't have the private key to decrypt, there is no way to provide true security and authentication.
|
||||||
|
|
||||||
|
Future versions may add additional options here for security.
|
||||||
|
|
||||||
|
Bots have no authentication and once a robot has been configured to access your WiFi it never needs to communicate with EcoVac's servers again.
|
||||||
|
|
||||||
|
----
|
||||||
|
### XMPP
|
||||||
|
|
||||||
|
*Example Model:* Deebot M81 Pro
|
||||||
|
|
||||||
|
The robot utilizes XMPP for control. When the robot boots up it sends a HTTP request to `lbo.ecovacs.net:8007` asking for the IP address and port of the XMPP server. Because of our DNS override, this request will be received by Bumper. We tell the robot to connect over XMPP to our local machine.
|
||||||
|
|
||||||
|
Both the app and robot connect to the central XMPP server, which relays messages between the app and robot. The messages contain commands for the robot to execute or responses and statuses of the robot.
|
||||||
|
|
||||||
|
***Bumper*** exposes a simulated XMPP server that implements the necessary functions for relaying messages between a robot and a client, acting as the central server.
|
||||||
|
|
||||||
|
**Note:** It's been observed that the apps will attempt to utilize XMPP regardless of robot model. For models that utilize MQTT, no activity is performed over XMPP just pings.
|
||||||
|
|
||||||
|
----
|
||||||
|
### MQTT
|
||||||
|
|
||||||
|
*Example Model:* Deebot 900/901
|
||||||
|
|
||||||
|
The robot utilizes MQTT for control. On startup it connects to an MQTT broker (`mq-{countrycode}.ecovacs.com`) and subscribes to a `p2p` topic where commands are issued.
|
||||||
|
The app also connects to the MQTT broker and subscribes to a `attr` topic where the robot will periodically post status and location updates.
|
||||||
|
|
||||||
|
The app receives status updates, for example battery status or robot position information, periodically via the `attr` topic.
|
||||||
|
|
||||||
|
However, commands issued via the app/client are not published directly to the `p2p` topic that the robot subscribes to. When the app sends a command such as "Clean", this is sent via a Rest API to the server at `portal-{countrycode}.ecouser.net`.
|
||||||
|
|
||||||
|
The Rest API provided by `portal-{countrycode}.ecouser.net` receives the command and passes it to a "helper" bot that has permission to publish to the `p2p` topic the robot is subscribed to. The "helper" bot waits for a response to be published by the robot and then passes that response back to the server for the Rest API to send back as a response.
|
||||||
|
|
||||||
|
***Bumper*** provides a simulated RestAPI and "helper" bot, performing the same function as the central server above.
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
A big thanks to the original project creator @torbjornaxelsson, without his work this project would have taken much longer to build.
|
||||||
|
|
||||||
Bumper woulden't exist without [Sucks](https://github.com/wpietri/sucks),
|
Bumper wouldn't exist without [Sucks](https://github.com/wpietri/sucks), an open source client for Ecovacs robots. Big thanks to @wpietri and contributors!
|
||||||
an open source client for Ecovacs robots. Big thanks to @wpietri and
|
|
||||||
contributors!
|
|
||||||
|
|
|
||||||
116
bumper.py
116
bumper.py
|
|
@ -3,17 +3,115 @@
|
||||||
import logging
|
import logging
|
||||||
import bumper
|
import bumper
|
||||||
import sys, socket
|
import sys, socket
|
||||||
|
import time
|
||||||
|
import platform
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO,
|
|
||||||
format='%(asctime)s %(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
conf_address = (socket.gethostbyname(socket.gethostname()), 8007)
|
def main():
|
||||||
xmpp_address = (socket.gethostbyname(socket.gethostname()), 5223)
|
args = sys.argv
|
||||||
|
|
||||||
# start conf server (async)
|
if len(args) > 0:
|
||||||
conf_server = bumper.ConfServer(conf_address, ssl=False, async=True)
|
if "--debug" in args:
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(message)s",
|
||||||
|
)
|
||||||
|
# format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s")
|
||||||
|
|
||||||
# start xmpp server (sync)
|
if platform.system() == "Darwin": # If a Mac, use 0.0.0.0 for listening
|
||||||
xmpp_server = bumper.XMPPServer(xmpp_address)
|
listen_host = "0.0.0.0"
|
||||||
|
else:
|
||||||
|
listen_host = socket.gethostbyname(socket.gethostname())
|
||||||
|
# listen_host = "localhost" #Try this if the above doesn't work
|
||||||
|
|
||||||
conf_server.disconnect()
|
conf_address_443 = (listen_host, 443)
|
||||||
|
conf_address_8007 = (listen_host, 8007)
|
||||||
|
xmpp_address = (listen_host, 5223)
|
||||||
|
mqtt_address = (listen_host, 8883)
|
||||||
|
|
||||||
|
xmpp_server = bumper.XMPPServer(
|
||||||
|
xmpp_address,
|
||||||
|
bumper_users=bumper.bumper_users_var,
|
||||||
|
bumper_bots=bumper.bumper_bots_var,
|
||||||
|
bumper_clients=bumper.bumper_clients_var,
|
||||||
|
)
|
||||||
|
mqtt_server = bumper.MQTTServer(
|
||||||
|
mqtt_address,
|
||||||
|
bumper_users=bumper.bumper_users_var,
|
||||||
|
bumper_bots=bumper.bumper_bots_var,
|
||||||
|
bumper_clients=bumper.bumper_clients_var,
|
||||||
|
)
|
||||||
|
mqtt_helperbot = bumper.MQTTHelperBot(
|
||||||
|
mqtt_address,
|
||||||
|
bumper_bots=bumper.bumper_bots_var,
|
||||||
|
bumper_clients=bumper.bumper_clients_var,
|
||||||
|
)
|
||||||
|
conf_server = bumper.ConfServer(
|
||||||
|
conf_address_443,
|
||||||
|
usessl=True,
|
||||||
|
bumper_users=bumper.bumper_users_var,
|
||||||
|
bumper_bots=bumper.bumper_bots_var,
|
||||||
|
bumper_clients=bumper.bumper_clients_var,
|
||||||
|
helperbot=mqtt_helperbot,
|
||||||
|
)
|
||||||
|
conf_server_2 = bumper.ConfServer(
|
||||||
|
conf_address_8007,
|
||||||
|
usessl=False,
|
||||||
|
bumper_users=bumper.bumper_users_var,
|
||||||
|
bumper_bots=bumper.bumper_bots_var,
|
||||||
|
bumper_clients=bumper.bumper_clients_var,
|
||||||
|
helperbot=mqtt_helperbot,
|
||||||
|
)
|
||||||
|
|
||||||
|
# add user
|
||||||
|
# users = bumper.bumper_users_var.get()
|
||||||
|
# user1 = bumper.BumperUser('user1')
|
||||||
|
# user1.add_device('devid')
|
||||||
|
# user1.add_bot('bot_did')
|
||||||
|
# users.append(user1)
|
||||||
|
# bumper.bumper_users_var.set(users)
|
||||||
|
|
||||||
|
# start xmpp server on port 5223 (sync)
|
||||||
|
xmpp_server.run(run_async=True) # Start in new thread
|
||||||
|
|
||||||
|
# start mqtt server on port 8883 (async)
|
||||||
|
mqtt_server.run(run_async=True) # Start in new thread
|
||||||
|
|
||||||
|
time.sleep(1.5) # Wait for broker startup
|
||||||
|
|
||||||
|
# start mqtt_helperbot (async)
|
||||||
|
mqtt_helperbot.run(run_async=True) # Start in new thread
|
||||||
|
|
||||||
|
# start conf server on port 443 (async) - Used for most https calls
|
||||||
|
conf_server.run(run_async=True) # Start in new thread
|
||||||
|
|
||||||
|
# start conf server on port 8007 (async) - Used for a load balancer request
|
||||||
|
conf_server_2.run(run_async=True) # Start in new thread
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
# WIP: Remove clients that have disconnected
|
||||||
|
# remove_clients = bumper.bumper_removeclients_var.get()
|
||||||
|
# if len(remove_clients) > 0:
|
||||||
|
# for uid in remove_clients:
|
||||||
|
# if uid != "":
|
||||||
|
# xmpp_server.remove_client_byuid(uid) #Remove clients from xmpp server
|
||||||
|
# remove_clients.remove(uid)
|
||||||
|
|
||||||
|
# bumper.bumper_removeclients_var.set(remove_clients)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
bumper.bumperlog.info("Bumper Exiting - Keyboard Interrupt")
|
||||||
|
print("Bumper Exiting")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,213 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from .confserver import ConfServer
|
from .confserver import ConfServer
|
||||||
|
from .mqttserver import MQTTServer
|
||||||
|
from .mqttserver import MQTTHelperBot
|
||||||
from .xmppserver import XMPPServer
|
from .xmppserver import XMPPServer
|
||||||
|
import asyncio
|
||||||
|
import contextvars
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
from base64 import b64decode, b64encode
|
||||||
|
|
||||||
|
bumper_users_var = contextvars.ContextVar("bumper_users", default=[])
|
||||||
|
bumper_clients_var = contextvars.ContextVar("bumper_clients", default=[])
|
||||||
|
bumper_bots_var = contextvars.ContextVar("bumper_bots", default=[])
|
||||||
|
|
||||||
|
ca_cert = "./certs/CA/cacert.pem"
|
||||||
|
server_cert = "./certs/cert.pem"
|
||||||
|
server_key = "./certs/key.pem"
|
||||||
|
|
||||||
|
use_auth = False
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
bumperlog = logging.getLogger("bumper")
|
||||||
|
confserverlog = logging.getLogger("confserver")
|
||||||
|
# Override the logging level
|
||||||
|
# confserverlog.setLevel(logging.INFO)
|
||||||
|
mqttserverlog = logging.getLogger("mqttserver")
|
||||||
|
# Override the logging level
|
||||||
|
# mqttserverlog.setLevel(logging.INFO)
|
||||||
|
helperbotlog = logging.getLogger("helperbot")
|
||||||
|
# Override the logging level
|
||||||
|
# helperbotlog.setLevel(logging.INFO)
|
||||||
|
xmppserverlog = logging.getLogger("xmppserver")
|
||||||
|
# Override the logging level
|
||||||
|
# xmppserverlog.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
def get_milli_time(timetoconvert):
|
||||||
|
return int(round(timetoconvert * 1000))
|
||||||
|
|
||||||
|
|
||||||
|
class BumperUser(object):
|
||||||
|
def __init__(self, userid=""):
|
||||||
|
self.userid = userid
|
||||||
|
self.devices = []
|
||||||
|
self.tokens = []
|
||||||
|
self.authcodes = []
|
||||||
|
self.bots = []
|
||||||
|
|
||||||
|
def add_device(self, devid):
|
||||||
|
if not devid in self.devices:
|
||||||
|
self.devices.append(devid)
|
||||||
|
|
||||||
|
def remove_device(self, devid):
|
||||||
|
if devid in self.devices:
|
||||||
|
self.devices.remove(devid)
|
||||||
|
|
||||||
|
def add_token(self, token):
|
||||||
|
if not token in self.tokens:
|
||||||
|
self.tokens.append(token)
|
||||||
|
|
||||||
|
def revoke_token(self, token):
|
||||||
|
if token in self.tokens:
|
||||||
|
self.tokens.remove(token)
|
||||||
|
|
||||||
|
def add_authcode(self, authcode):
|
||||||
|
if not authcode in self.authcodes:
|
||||||
|
self.authcodes.append(authcode)
|
||||||
|
|
||||||
|
def revoke_authcode(self, authcode):
|
||||||
|
if authcode in self.authcodes:
|
||||||
|
self.authcodes.remove(authcode)
|
||||||
|
|
||||||
|
def add_bot(self, botdid):
|
||||||
|
if not botdid in self.bots:
|
||||||
|
self.bots.append(botdid)
|
||||||
|
|
||||||
|
def remove_bot(self, botdid):
|
||||||
|
if botdid in self.bots:
|
||||||
|
self.bots.remove(botdid)
|
||||||
|
|
||||||
|
|
||||||
|
class VacBotDevice(object):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
did="",
|
||||||
|
vac_bot_device_class="",
|
||||||
|
resource="",
|
||||||
|
name="",
|
||||||
|
nick="",
|
||||||
|
company="eco-ng",
|
||||||
|
):
|
||||||
|
self.vac_bot_device_class = vac_bot_device_class
|
||||||
|
self.company = company
|
||||||
|
self.did = did
|
||||||
|
self.name = name
|
||||||
|
self.nick = nick
|
||||||
|
self.resource = resource
|
||||||
|
self.mqtt_connection = False
|
||||||
|
self.xmpp_connection = False
|
||||||
|
|
||||||
|
def asdict(self):
|
||||||
|
return {
|
||||||
|
"class": self.vac_bot_device_class,
|
||||||
|
"company": self.company,
|
||||||
|
"did": self.did,
|
||||||
|
"name": self.name,
|
||||||
|
"nick": self.nick,
|
||||||
|
"resource": self.resource,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VacBotClient(object):
|
||||||
|
def __init__(self, userid="", realm="", token=""):
|
||||||
|
self.userid = userid
|
||||||
|
self.realm = realm
|
||||||
|
self.resource = token
|
||||||
|
self.mqtt_connection = False
|
||||||
|
self.xmpp_connection = False
|
||||||
|
|
||||||
|
def asdict(self):
|
||||||
|
return {"userid": self.userid, "realm": self.realm, "resource": self.resource}
|
||||||
|
|
||||||
|
|
||||||
|
def check_authcode(uid, authcode):
|
||||||
|
users = bumper_users_var.get()
|
||||||
|
for user in users:
|
||||||
|
if uid == "fuid_{}".format(user.userid) and authcode in user.authcodes:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def add_bot(sn, did, devclass, resource):
|
||||||
|
|
||||||
|
newbot = VacBotDevice()
|
||||||
|
newbot.did = did
|
||||||
|
newbot.name = sn
|
||||||
|
newbot.vac_bot_device_class = devclass
|
||||||
|
newbot.resource = resource
|
||||||
|
|
||||||
|
bots = bumper_bots_var.get()
|
||||||
|
existingbot = False
|
||||||
|
for bot in bots:
|
||||||
|
if bot.did == newbot.did:
|
||||||
|
existingbot = True
|
||||||
|
|
||||||
|
if existingbot == False:
|
||||||
|
bots.append(newbot)
|
||||||
|
bumperlog.info("new bot added SN: {} DID: {}".format(newbot.name, newbot.did))
|
||||||
|
bumper_bots_var.set(bots)
|
||||||
|
|
||||||
|
|
||||||
|
def add_client(userid, realm, resource):
|
||||||
|
|
||||||
|
newclient = VacBotClient()
|
||||||
|
newclient.userid = userid
|
||||||
|
newclient.realm = realm
|
||||||
|
newclient.resource = resource
|
||||||
|
|
||||||
|
clients = bumper_clients_var.get()
|
||||||
|
|
||||||
|
existingclient = False
|
||||||
|
for client in clients:
|
||||||
|
if client.userid == newclient.userid:
|
||||||
|
existingclient = True
|
||||||
|
|
||||||
|
if existingclient == False:
|
||||||
|
clients.append(newclient)
|
||||||
|
bumperlog.info("new client added {}".format(newclient.userid))
|
||||||
|
bumper_clients_var.set(clients)
|
||||||
|
|
||||||
|
|
||||||
|
RETURN_API_SUCCESS = "0000"
|
||||||
|
ERR_ACTIVATE_TOKEN_TIMEOUT = "1006"
|
||||||
|
ERR_COMMON = "0001"
|
||||||
|
ERR_DEFAULT = "9000"
|
||||||
|
ERR_EMAIL_NON_EXIST = "1002"
|
||||||
|
ERR_EMAIL_SEND_TIME_LIMIT = "1011"
|
||||||
|
ERR_EMAIL_USED = "1001"
|
||||||
|
ERR_INTERFACE_AUTH = "0002"
|
||||||
|
ERR_PARAM_INVALID = "0003"
|
||||||
|
ERR_PWD_WRONG = "1005"
|
||||||
|
ERR_RESET_PWD_TOKEN_TIMEOUT = "1007"
|
||||||
|
ERR_TIMESTAMP_INVALID = "0005"
|
||||||
|
ERR_TOKEN_INVALID = "0004"
|
||||||
|
ERR_USER_DISABLE = "1004"
|
||||||
|
ERR_USER_NOT_ACTIVATED = "1003"
|
||||||
|
ERR_WRONG_COMFIRM_PWD = "10010"
|
||||||
|
ERR_WRONG_EMAIL_ADDRESS = "1008"
|
||||||
|
ERR_WRONG_PWD_FROMATE = "1009"
|
||||||
|
|
||||||
|
API_ERRORS = {
|
||||||
|
RETURN_API_SUCCESS: "0000",
|
||||||
|
ERR_ACTIVATE_TOKEN_TIMEOUT: "1006",
|
||||||
|
ERR_COMMON: "0001",
|
||||||
|
ERR_DEFAULT: "9000",
|
||||||
|
ERR_EMAIL_NON_EXIST: "1002",
|
||||||
|
ERR_EMAIL_SEND_TIME_LIMIT: "1011",
|
||||||
|
ERR_EMAIL_USED: "1001",
|
||||||
|
ERR_INTERFACE_AUTH: "0002",
|
||||||
|
ERR_PARAM_INVALID: "0003",
|
||||||
|
ERR_PWD_WRONG: "1005",
|
||||||
|
ERR_RESET_PWD_TOKEN_TIMEOUT: "1007",
|
||||||
|
ERR_TIMESTAMP_INVALID: "0005",
|
||||||
|
ERR_TOKEN_INVALID: "0004",
|
||||||
|
ERR_USER_DISABLE: "1004",
|
||||||
|
ERR_USER_NOT_ACTIVATED: "1003",
|
||||||
|
ERR_WRONG_COMFIRM_PWD: "10010",
|
||||||
|
ERR_WRONG_EMAIL_ADDRESS: "1008",
|
||||||
|
ERR_WRONG_PWD_FROMATE: "1009",
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,88 +1,660 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from http.server import HTTPServer
|
|
||||||
from http.server import BaseHTTPRequestHandler
|
|
||||||
from http import HTTPStatus
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import socket, logging, ssl, json, sys
|
import socket, logging, ssl, json
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import bumper
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import asyncio
|
||||||
|
import contextvars
|
||||||
|
from aiohttp import web
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
class RequestHandler(BaseHTTPRequestHandler):
|
class aiohttp_filter(logging.Filter):
|
||||||
def do_POST(self):
|
def filter(self, record):
|
||||||
|
if (
|
||||||
|
record.name == "aiohttp.access" and record.levelno == 20
|
||||||
|
): # Filters aiohttp.access log to switch it from INFO to DEBUG
|
||||||
|
record.levelno = 10
|
||||||
|
record.levelname = "DEBUG"
|
||||||
|
|
||||||
|
if (
|
||||||
|
record.levelno == 10
|
||||||
|
and logging.getLogger("confserver").getEffectiveLevel() == 10
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
confserverlog = logging.getLogger("confserver")
|
||||||
|
|
||||||
|
logging.getLogger("asyncio").setLevel(logging.CRITICAL + 1) # Ignore this logger
|
||||||
|
logging.getLogger("aiohttp.access").addFilter(aiohttp_filter())
|
||||||
|
|
||||||
|
|
||||||
|
class ConfServer:
|
||||||
|
bumper_clients = contextvars.ContextVar
|
||||||
|
bumper_bots = contextvars.ContextVar
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
address,
|
||||||
|
usessl=False,
|
||||||
|
bumper_users=contextvars.ContextVar,
|
||||||
|
bumper_bots=contextvars.ContextVar,
|
||||||
|
bumper_clients=contextvars.ContextVar,
|
||||||
|
helperbot=None,
|
||||||
|
):
|
||||||
|
self.bumper_users = bumper_users
|
||||||
|
self.bumper_bots = bumper_bots
|
||||||
|
self.bumper_clients = bumper_clients
|
||||||
|
self.helperbot = helperbot
|
||||||
|
self.usessl = usessl
|
||||||
|
self.address = address
|
||||||
|
self.confthread = None
|
||||||
|
|
||||||
|
def run(self, run_async=False):
|
||||||
try:
|
try:
|
||||||
self.protocol_version = 'HTTP/1.1'
|
if run_async:
|
||||||
content_length = int(self.headers['Content-Length'])
|
confserverlog.debug("Starting ConfServer Thread: 1")
|
||||||
post_data = self.rfile.read(content_length)
|
self.confthread = Thread(
|
||||||
logging.debug("Headers: " + str(self.headers))
|
name="ConfServer_{}_Thread".format(self.address[1]),
|
||||||
request_body = post_data.decode('utf-8')
|
target=self.run_server,
|
||||||
logging.debug("Request: " + request_body)
|
)
|
||||||
json_body = json.loads(request_body)
|
self.confthread.setDaemon(True)
|
||||||
todo = json_body['todo']
|
self.confthread.start()
|
||||||
if todo == 'FindBest':
|
|
||||||
service = json_body['service']
|
|
||||||
if service == 'EcoMsgNew':
|
|
||||||
body = '{{"result":"ok","ip":"{}","port":5223}}'.format(socket.gethostbyname(socket.gethostname()))
|
|
||||||
elif service == 'EcoUpdate':
|
|
||||||
body = '{"result":"ok","ip":"47.88.66.164","port":8005}'
|
|
||||||
elif todo == 'loginByItToken':
|
|
||||||
body = "{{'todo': 'result', 'result': 'ok', 'userId': '{}', 'resource': '{}', 'token': '{}'}}".format(json_body['userId'], json_body['resource'], json_body['token'])
|
|
||||||
elif todo == 'GetDeviceList':
|
|
||||||
body = "{'todo': 'result', 'result': 'ok', 'devices': [{'did': '{}', 'name': '{}', 'class': '{}', 'resource': 'atom', 'nick': None, 'company': 'eco'}]}"
|
|
||||||
logging.debug("Response: " + body)
|
|
||||||
body = body.encode()
|
|
||||||
self.send_response(HTTPStatus.OK)
|
|
||||||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
self.send_header('Connection', 'keep-alive')
|
|
||||||
self.send_header('Content-Length', len(body))
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(body)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error('ConfServer: {}'.format(e))
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPServerThread(HTTPServer, Thread):
|
|
||||||
def __init__(self, server_address):
|
|
||||||
Thread.__init__(self)
|
|
||||||
self.server_address = server_address
|
|
||||||
self.handler = RequestHandler
|
|
||||||
self.exit_flag = False
|
|
||||||
def handle_error(self, request, client_address):
|
|
||||||
self.close_request(request)
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
HTTPServer.__init__(self, self.server_address, self.handler)
|
|
||||||
logging.info('ConfServer: listening on {}:{}'.format(self.server_address[0], self.server_address[1]))
|
|
||||||
while not self.exit_flag:
|
|
||||||
self.handle_request()
|
|
||||||
except Exception as e:
|
|
||||||
logging.error('ConfServer: {}'.format(e))
|
|
||||||
def disconnect(self):
|
|
||||||
self.exit_flag = True
|
|
||||||
# make a connection to
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
s.connect(self.server_address)
|
|
||||||
s.close()
|
|
||||||
|
|
||||||
|
|
||||||
class ConfServer():
|
|
||||||
def __init__(self, address, ssl=False, async=True):
|
|
||||||
try:
|
|
||||||
self.async = async
|
|
||||||
self.server = HTTPServerThread(address)
|
|
||||||
if ssl:
|
|
||||||
self.server.socket = ssl.wrap_socket(self.server.socket, keyfile='./certs/key.pem', certfile='./certs/cert.pem', server_side=True)
|
|
||||||
if self.async:
|
|
||||||
self.server.start()
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.server.run()
|
self.run_server()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error('ConfServer: {}'.format(e))
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def run_server(self):
|
||||||
|
logging.info("Starting ConfServer at {}".format(self.address))
|
||||||
|
print("Starting ConfServer at {}".format(self.address))
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
except:
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
|
||||||
|
try:
|
||||||
|
loop.run_until_complete(self.start_server())
|
||||||
|
loop.run_forever()
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def start_server(self):
|
||||||
|
try:
|
||||||
|
app = web.Application()
|
||||||
|
|
||||||
|
app.add_routes(
|
||||||
|
[
|
||||||
|
web.get("", self.handle_base),
|
||||||
|
web.get(
|
||||||
|
"/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/login",
|
||||||
|
self.handle_login,
|
||||||
|
),
|
||||||
|
web.get(
|
||||||
|
"/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin",
|
||||||
|
self.handle_login,
|
||||||
|
),
|
||||||
|
web.get(
|
||||||
|
"/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/logout",
|
||||||
|
self.handle_logout,
|
||||||
|
),
|
||||||
|
web.get(
|
||||||
|
"/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getAuthCode",
|
||||||
|
self.handle_getAuthCode,
|
||||||
|
),
|
||||||
|
web.get(
|
||||||
|
"/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreement",
|
||||||
|
self.handle_checkAgreement,
|
||||||
|
),
|
||||||
|
web.get(
|
||||||
|
"/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkVersion",
|
||||||
|
self.handle_checkVersion,
|
||||||
|
),
|
||||||
|
web.get(
|
||||||
|
"/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/campaign/homePageAlert",
|
||||||
|
self.handle_homePageAlert,
|
||||||
|
),
|
||||||
|
web.post("/api/users/user.do", self.handle_usersapi),
|
||||||
|
web.get("/api/users/user.do", self.handle_usersapi),
|
||||||
|
web.post(
|
||||||
|
"/api/pim/product/getProductIotMap",
|
||||||
|
self.handle_getProductIotMap,
|
||||||
|
),
|
||||||
|
web.post(
|
||||||
|
"/api/iot/devmanager.do", self.handle_devmanager_botcommand
|
||||||
|
),
|
||||||
|
web.post("/lookup.do", self.handle_lookup),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# Direct register from app:
|
||||||
|
# /{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister
|
||||||
|
|
||||||
|
runner = web.AppRunner(app)
|
||||||
|
await runner.setup()
|
||||||
|
|
||||||
|
if self.usessl:
|
||||||
|
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||||
|
ssl_ctx.load_cert_chain(bumper.server_cert, bumper.server_key)
|
||||||
|
site = web.TCPSite(
|
||||||
|
runner,
|
||||||
|
host=self.address[0],
|
||||||
|
port=self.address[1],
|
||||||
|
ssl_context=ssl_ctx,
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
site = web.TCPSite(runner, host=self.address[0], port=self.address[1])
|
||||||
|
|
||||||
|
await site.start()
|
||||||
|
|
||||||
|
except PermissionError as e:
|
||||||
|
if "bind" in e.strerror:
|
||||||
|
confserverlog.exception(
|
||||||
|
"Error binding confserver, exiting. Try using a different hostname or IP - {}".format(
|
||||||
|
e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
async def handle_base(self, request):
|
||||||
|
try:
|
||||||
|
|
||||||
|
text = "Bumper!"
|
||||||
|
|
||||||
|
return web.json_response(text)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_login(self, request):
|
||||||
|
try:
|
||||||
|
user_devid = request.match_info.get("devid", "")
|
||||||
|
countrycode = request.match_info.get("country", "us")
|
||||||
|
confserverlog.info(
|
||||||
|
"client with devid {} attempting login".format(user_devid)
|
||||||
|
)
|
||||||
|
if bumper.use_auth:
|
||||||
|
if (
|
||||||
|
not user_devid == ""
|
||||||
|
): # Performing basic "auth" using devid, super insecure
|
||||||
|
users = self.bumper_users.get()
|
||||||
|
for user in users:
|
||||||
|
if user_devid in user.devices:
|
||||||
|
tmpaccesstoken = ""
|
||||||
|
if "checkLogin" in request.path:
|
||||||
|
if request.query[
|
||||||
|
"accessToken"
|
||||||
|
] in user.tokens and request.query[
|
||||||
|
"uid"
|
||||||
|
] == "fuid_{}".format(
|
||||||
|
user.userid
|
||||||
|
):
|
||||||
|
tmpaccesstoken = request.query["accessToken"]
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": {
|
||||||
|
"accessToken": tmpaccesstoken, # Random chars 32 length
|
||||||
|
"country": countrycode,
|
||||||
|
"email": "null@null.com",
|
||||||
|
"uid": "fuid_{}".format(user.userid),
|
||||||
|
"username": "fusername_{}".format(
|
||||||
|
user.userid
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"msg": "操作成功",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
body = {
|
||||||
|
"code": bumper.ERR_TOKEN_INVALID,
|
||||||
|
"data": None,
|
||||||
|
"msg": "当前密码错误",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
if tmpaccesstoken == "":
|
||||||
|
tmpaccesstoken = uuid.uuid4().hex
|
||||||
|
user.add_token(tmpaccesstoken)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": {
|
||||||
|
"accessToken": tmpaccesstoken, # Random chars 32 length
|
||||||
|
"country": countrycode,
|
||||||
|
"email": "null@null.com",
|
||||||
|
"uid": "fuid_{}".format(user.userid),
|
||||||
|
"username": "fusername_{}".format(user.userid),
|
||||||
|
},
|
||||||
|
"msg": "操作成功",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
self.bumper_users.set(users)
|
||||||
|
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"code": bumper.ERR_USER_NOT_ACTIVATED,
|
||||||
|
"data": None,
|
||||||
|
"msg": "当前密码错误",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return web.json_response(body)
|
||||||
|
else:
|
||||||
|
return web.json_response(
|
||||||
|
self._auth_any(user_devid, countrycode, request)
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def _auth_any(self, devid, country, request):
|
||||||
|
try:
|
||||||
|
user_devid = devid
|
||||||
|
countrycode = country
|
||||||
|
|
||||||
|
tmpaccesstoken = ""
|
||||||
|
users = self.bumper_users.get()
|
||||||
|
bots = self.bumper_bots.get()
|
||||||
|
|
||||||
|
if len(users) > 0:
|
||||||
|
tmpuser = users[0]
|
||||||
|
tmpuser.add_device(user_devid)
|
||||||
|
else:
|
||||||
|
tmpuser = bumper.BumperUser("tmpuser")
|
||||||
|
users.append(tmpuser)
|
||||||
|
tmpuser.add_device(user_devid)
|
||||||
|
|
||||||
|
for bot in bots:
|
||||||
|
tmpuser.add_bot(bot.did)
|
||||||
|
|
||||||
|
if "checkLogin" in request.path:
|
||||||
|
tmpaccesstoken = request.query["accessToken"]
|
||||||
|
tmpuser.add_token(tmpaccesstoken)
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": {
|
||||||
|
"accessToken": tmpaccesstoken, # Random chars 32 length
|
||||||
|
"country": countrycode,
|
||||||
|
"email": "null@null.com",
|
||||||
|
"uid": "fuid_{}".format(tmpuser.userid),
|
||||||
|
"username": "fusername_{}".format(tmpuser.userid),
|
||||||
|
},
|
||||||
|
"msg": "操作成功",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
if tmpaccesstoken == "":
|
||||||
|
tmpaccesstoken = uuid.uuid4().hex
|
||||||
|
tmpuser.add_token(tmpaccesstoken)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": {
|
||||||
|
"accessToken": tmpaccesstoken, # Random chars 32 length
|
||||||
|
"country": countrycode,
|
||||||
|
"email": "null@null.com",
|
||||||
|
"uid": "fuid_{}".format(tmpuser.userid),
|
||||||
|
"username": "fusername_{}".format(tmpuser.userid),
|
||||||
|
},
|
||||||
|
"msg": "操作成功",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
self.bumper_users.set(users)
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_logout(self, request):
|
||||||
|
try:
|
||||||
|
user_devid = request.match_info.get("devid", "")
|
||||||
|
if not user_devid == "":
|
||||||
|
users = self.bumper_users.get()
|
||||||
|
for user in users:
|
||||||
|
if user_devid in user.devices:
|
||||||
|
if (
|
||||||
|
request.query["uid"] == "fuid_{}".format(user.userid)
|
||||||
|
and request.query["accessToken"] in user.tokens
|
||||||
|
):
|
||||||
|
user.revoke_token(request.query["accessToken"])
|
||||||
|
self.bumper_users.set(users)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": None,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_getAuthCode(self, request):
|
||||||
|
try:
|
||||||
|
|
||||||
|
user_devid = request.match_info.get("devid", "")
|
||||||
|
if not user_devid == "":
|
||||||
|
users = self.bumper_users.get()
|
||||||
|
if len(users) > 0:
|
||||||
|
for user in users:
|
||||||
|
if (
|
||||||
|
user_devid in user.devices
|
||||||
|
and request.query["accessToken"] in user.tokens
|
||||||
|
):
|
||||||
|
countrycode = request.match_info.get("country", "us")
|
||||||
|
tmpauthcode = "{}_{}".format(countrycode, uuid.uuid4().hex)
|
||||||
|
user.add_authcode(tmpauthcode)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": {
|
||||||
|
"authCode": tmpauthcode,
|
||||||
|
"ecovacsUid": request.query["uid"],
|
||||||
|
},
|
||||||
|
"msg": "操作成功",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
self.bumper_users.set(users)
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"code": bumper.ERR_TOKEN_INVALID,
|
||||||
|
"data": None,
|
||||||
|
"msg": "当前密码错误",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_checkVersion(self, request):
|
||||||
|
try:
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": {
|
||||||
|
"c": None,
|
||||||
|
"img": None,
|
||||||
|
"r": 0,
|
||||||
|
"t": None,
|
||||||
|
"u": None,
|
||||||
|
"ut": 0,
|
||||||
|
"v": None,
|
||||||
|
},
|
||||||
|
"msg": "操作成功",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_checkAgreement(self, request):
|
||||||
|
try:
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": [],
|
||||||
|
"msg": "操作成功",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_homePageAlert(self, request):
|
||||||
|
try:
|
||||||
|
nextAlert = bumper.get_milli_time(
|
||||||
|
(datetime.now() + timedelta(hours=12)).timestamp()
|
||||||
|
)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": {
|
||||||
|
"clickSchemeUrl": None,
|
||||||
|
"clickWebUrl": None,
|
||||||
|
"hasCampaign": "N",
|
||||||
|
"imageUrl": None,
|
||||||
|
"nextAlertTime": nextAlert,
|
||||||
|
"serverTime": bumper.get_milli_time(time.time()),
|
||||||
|
},
|
||||||
|
"msg": "操作成功",
|
||||||
|
"time": bumper.get_milli_time(time.time()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_getProductIotMap(self, request):
|
||||||
|
try:
|
||||||
|
body = {
|
||||||
|
"code": bumper.RETURN_API_SUCCESS,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"classid": "dl8fht",
|
||||||
|
"product": {
|
||||||
|
"_id": "5acb0fa87c295c0001876ecf",
|
||||||
|
"name": "DEEBOT 600 Series",
|
||||||
|
"icon": "5acc32067c295c0001876eea",
|
||||||
|
"UILogicId": "dl8fht",
|
||||||
|
"ota": False,
|
||||||
|
"iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"classid": "02uwxm",
|
||||||
|
"product": {
|
||||||
|
"_id": "5ae1481e7ccd1a0001e1f69e",
|
||||||
|
"name": "DEEBOT OZMO Slim10 Series",
|
||||||
|
"icon": "5b1dddc48bc45700014035a1",
|
||||||
|
"UILogicId": "02uwxm",
|
||||||
|
"ota": False,
|
||||||
|
"iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"classid": "y79a7u",
|
||||||
|
"product": {
|
||||||
|
"_id": "5b04c0227ccd1a0001e1f6a8",
|
||||||
|
"name": "DEEBOT OZMO 900",
|
||||||
|
"icon": "5b04c0217ccd1a0001e1f6a7",
|
||||||
|
"UILogicId": "y79a7u",
|
||||||
|
"ota": True,
|
||||||
|
"iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"classid": "jr3pqa",
|
||||||
|
"product": {
|
||||||
|
"_id": "5b43077b8bc457000140363e",
|
||||||
|
"name": "DEEBOT 711",
|
||||||
|
"icon": "5b5ac4cc8d5a56000111e769",
|
||||||
|
"UILogicId": "jr3pqa",
|
||||||
|
"ota": True,
|
||||||
|
"iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"classid": "uv242z",
|
||||||
|
"product": {
|
||||||
|
"_id": "5b5149b4ac0b87000148c128",
|
||||||
|
"name": "DEEBOT 710",
|
||||||
|
"icon": "5b5ac4e45f21100001882bb9",
|
||||||
|
"UILogicId": "uv242z",
|
||||||
|
"ota": True,
|
||||||
|
"iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"classid": "ls1ok3",
|
||||||
|
"product": {
|
||||||
|
"_id": "5b6561060506b100015c8868",
|
||||||
|
"name": "DEEBOT 900 Series",
|
||||||
|
"icon": "5ba4a2cb6c2f120001c32839",
|
||||||
|
"UILogicId": "ls1ok3",
|
||||||
|
"ota": True,
|
||||||
|
"iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_usersapi(self, request):
|
||||||
|
try:
|
||||||
|
|
||||||
|
body = {}
|
||||||
|
postbody = {}
|
||||||
|
if request.content_type == "application/x-www-form-urlencoded":
|
||||||
|
postbody = await request.post()
|
||||||
|
|
||||||
|
else:
|
||||||
|
postbody = json.loads(await request.text())
|
||||||
|
|
||||||
|
todo = postbody["todo"]
|
||||||
|
if todo == "FindBest":
|
||||||
|
service = postbody["service"]
|
||||||
|
if service == "EcoMsgNew":
|
||||||
|
body = {
|
||||||
|
"result": "ok",
|
||||||
|
"ip": socket.gethostbyname(socket.gethostname()),
|
||||||
|
"port": 5223,
|
||||||
|
}
|
||||||
|
elif service == "EcoUpdate":
|
||||||
|
body = {"result": "ok", "ip": "47.88.66.164", "port": 8005}
|
||||||
|
elif todo == "loginByItToken":
|
||||||
|
|
||||||
|
users = self.bumper_users.get()
|
||||||
|
for user in users:
|
||||||
|
if (
|
||||||
|
postbody["userId"] == "fuid_{}".format(user.userid)
|
||||||
|
and postbody["token"] in user.authcodes
|
||||||
|
):
|
||||||
|
body = {
|
||||||
|
"resource": postbody["resource"],
|
||||||
|
"result": "ok",
|
||||||
|
"todo": "result",
|
||||||
|
"token": postbody["token"],
|
||||||
|
"userId": postbody["userId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
elif todo == "GetDeviceList":
|
||||||
|
active_bots = self.bumper_bots.get()
|
||||||
|
bot_list = []
|
||||||
|
for bot in active_bots:
|
||||||
|
bot_list.append(bot.asdict())
|
||||||
|
body = {"devices": bot_list, "result": "ok", "todo": "result"}
|
||||||
|
|
||||||
|
elif todo == "SetDeviceNick":
|
||||||
|
bots = self.bumper_bots.get()
|
||||||
|
for bot in bots:
|
||||||
|
if postbody["did"] == bot.did:
|
||||||
|
bot.nick = postbody["nick"]
|
||||||
|
self.bumper_bots.set(bots)
|
||||||
|
body = {"result": "ok", "todo": "result"}
|
||||||
|
|
||||||
|
confserverlog.debug(
|
||||||
|
"\r\n POST: {} \r\n Response: {}".format(postbody, body)
|
||||||
|
)
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_lookup(self, request):
|
||||||
|
try:
|
||||||
|
|
||||||
|
body = {}
|
||||||
|
postbody = {}
|
||||||
|
if request.content_type == "application/x-www-form-urlencoded":
|
||||||
|
postbody = await request.post()
|
||||||
|
|
||||||
|
else:
|
||||||
|
postbody = json.loads(await request.text())
|
||||||
|
|
||||||
|
confserverlog.debug(postbody)
|
||||||
|
|
||||||
|
todo = postbody["todo"]
|
||||||
|
if todo == "FindBest":
|
||||||
|
service = postbody["service"]
|
||||||
|
if service == "EcoMsgNew":
|
||||||
|
body = {
|
||||||
|
"result": "ok",
|
||||||
|
"ip": socket.gethostbyname(socket.gethostname()),
|
||||||
|
"port": 5223,
|
||||||
|
}
|
||||||
|
elif service == "EcoUpdate":
|
||||||
|
body = {"result": "ok", "ip": "47.88.66.164", "port": 8005}
|
||||||
|
|
||||||
|
confserverlog.debug(
|
||||||
|
"\r\n POST: {} \r\n Response: {}".format(postbody, body)
|
||||||
|
)
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def handle_devmanager_botcommand(self, request):
|
||||||
|
try:
|
||||||
|
json_body = json.loads(await request.text())
|
||||||
|
randomid = "".join(random.sample(string.ascii_letters, 6))
|
||||||
|
bots = self.bumper_bots.get()
|
||||||
|
for bot in bots:
|
||||||
|
if bot.did == json_body["toId"] and bot.mqtt_connection == True:
|
||||||
|
retcmd = await self.helperbot.send_command(json_body, randomid)
|
||||||
|
body = retcmd
|
||||||
|
confserverlog.debug(
|
||||||
|
"\r\n POST: {} \r\n Response: {}".format(json_body, body)
|
||||||
|
)
|
||||||
|
return web.json_response(body)
|
||||||
|
else:
|
||||||
|
confserverlog.error(
|
||||||
|
"No bots with DID: {} connected to MQTT".format(
|
||||||
|
json_body["toId"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"}
|
||||||
|
return web.json_response(body)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
logging.info('ConfServer: shutting down...')
|
try:
|
||||||
self.server.disconnect()
|
confserverlog.info("shutting down")
|
||||||
if(self.async):
|
if self.run_async:
|
||||||
self.server.join()
|
self.confthread.join()
|
||||||
logging.info('ConfServer: bye')
|
else:
|
||||||
|
self.confthread.disconnect()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
confserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
|
|
||||||
419
bumper/mqttserver.py
Normal file
419
bumper/mqttserver.py
Normal file
|
|
@ -0,0 +1,419 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import hbmqtt
|
||||||
|
from hbmqtt.broker import Broker
|
||||||
|
from hbmqtt.client import MQTTClient
|
||||||
|
from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
|
||||||
|
import pkg_resources
|
||||||
|
import contextvars
|
||||||
|
import time
|
||||||
|
from threading import Thread
|
||||||
|
import ssl
|
||||||
|
import bumper
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
helperbotlog = logging.getLogger("helperbot")
|
||||||
|
mqttserverlog = logging.getLogger("mqttserver")
|
||||||
|
|
||||||
|
logging.getLogger("transitions").setLevel(logging.CRITICAL + 1) # Ignore this logger
|
||||||
|
logging.getLogger("passlib").setLevel(logging.CRITICAL + 1) # Ignore this logger
|
||||||
|
logging.getLogger("hbmqtt.broker").setLevel(
|
||||||
|
logging.CRITICAL + 1
|
||||||
|
) # Ignore this logger #There are some sublogs that could be set if needed (.plugins)
|
||||||
|
logging.getLogger("hbmqtt.mqtt.protocol").setLevel(
|
||||||
|
logging.CRITICAL + 1
|
||||||
|
) # Ignore this logger
|
||||||
|
logging.getLogger("hbmqtt.client").setLevel(logging.CRITICAL + 1) # Ignore this logger
|
||||||
|
|
||||||
|
|
||||||
|
class MQTTHelperBot:
|
||||||
|
|
||||||
|
Client = MQTTClient()
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
address,
|
||||||
|
bumper_bots=contextvars.ContextVar,
|
||||||
|
bumper_clients=contextvars.ContextVar,
|
||||||
|
):
|
||||||
|
self.address = address
|
||||||
|
self.client_id = "helper1@bumper/helper1"
|
||||||
|
self.command_responses = contextvars.ContextVar("command_responses", default=[])
|
||||||
|
self.helperthread = None
|
||||||
|
|
||||||
|
def run(self, run_async=False):
|
||||||
|
if run_async:
|
||||||
|
hloop = asyncio.new_event_loop()
|
||||||
|
helperbotlog.debug("Starting MQTT HelperBot Thread: 1")
|
||||||
|
self.helperthread = Thread(
|
||||||
|
name="MQTTHelperBot_Thread", target=self.run_helperbot, args=(hloop,)
|
||||||
|
)
|
||||||
|
self.helperthread.setDaemon(True)
|
||||||
|
self.helperthread.start()
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.run_helperbot()
|
||||||
|
|
||||||
|
def run_helperbot(self, loop):
|
||||||
|
logging.info("Starting MQTT HelperBot")
|
||||||
|
print("Starting MQTT HelperBot")
|
||||||
|
try:
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
self.Client = MQTTClient(
|
||||||
|
client_id=self.client_id, config={"check_hostname": False}
|
||||||
|
)
|
||||||
|
loop.run_until_complete(self.start_helper_bot())
|
||||||
|
loop.run_until_complete(self.get_msg())
|
||||||
|
loop.run_forever()
|
||||||
|
except Exception as e:
|
||||||
|
helperbotlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def start_helper_bot(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.Client.connect(
|
||||||
|
"mqtts://{}:{}/".format(self.address[0], self.address[1]),
|
||||||
|
cafile=bumper.ca_cert,
|
||||||
|
)
|
||||||
|
await self.Client.subscribe(
|
||||||
|
[
|
||||||
|
("iot/p2p/+/+/+/+/helper1/bumper/helper1/+/+/+", QOS_0),
|
||||||
|
("iot/p2p/+", QOS_0),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
helperbotlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def get_msg(self):
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
message = await self.Client.deliver_message()
|
||||||
|
|
||||||
|
# helperbotlog.debug("HelperBot MQTT Received Message on Topic: {} - Message: {}".format(message.topic, str(message.payload.decode("utf-8"))))
|
||||||
|
cresp = self.command_responses.get()
|
||||||
|
|
||||||
|
if str(message.topic).split("/")[6] == "helper1":
|
||||||
|
cresp.append(
|
||||||
|
{
|
||||||
|
"time": time.time(),
|
||||||
|
"topic": message.topic,
|
||||||
|
"payload": str(message.data.decode("utf-8")),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cleanup "expired messages" > 60 seconds from time
|
||||||
|
for msg in cresp:
|
||||||
|
expire_time = (
|
||||||
|
datetime.fromtimestamp(msg["time"]) + timedelta(seconds=10)
|
||||||
|
).timestamp()
|
||||||
|
if time.time() > expire_time:
|
||||||
|
# helperbotlog.debug("Pruning Message Time: {}, MsgTime: {}, MsgTime+60: {}".format(time.time(), msg['time'], expire_time))
|
||||||
|
cresp.remove(msg)
|
||||||
|
|
||||||
|
self.command_responses.set(cresp)
|
||||||
|
# helperbotlog.debug("MQTT Command Response List Count: %s" %len(cresp))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
helperbotlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def wait_for_resp(self, requestid):
|
||||||
|
try:
|
||||||
|
|
||||||
|
t_end = (datetime.now() + timedelta(seconds=10)).timestamp()
|
||||||
|
|
||||||
|
while time.time() < t_end:
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
responses = self.command_responses.get()
|
||||||
|
if len(responses) > 0:
|
||||||
|
for msg in responses:
|
||||||
|
topic = str(msg["topic"]).split("/")
|
||||||
|
if topic[6] == "helper1" and topic[10] == requestid:
|
||||||
|
# helperbotlog.debug('VacBot MQTT Response: Topic: %s Payload: %s' % (msg['topic'], msg['payload']))
|
||||||
|
if topic[11] == "j":
|
||||||
|
resppayload = json.loads(msg["payload"])
|
||||||
|
else:
|
||||||
|
resppayload = str(msg["payload"])
|
||||||
|
resp = {"id": requestid, "ret": "ok", "resp": resppayload}
|
||||||
|
cresp = self.command_responses.get()
|
||||||
|
cresp.remove(msg)
|
||||||
|
self.command_responses.set(cresp)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
return {"id": requestid, "errno": "timeout", "ret": "fail"}
|
||||||
|
except asyncio.CancelledError as e:
|
||||||
|
helperbotlog.debug("wait_for_resp cancelled by asyncio")
|
||||||
|
except Exception as e:
|
||||||
|
helperbotlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def send_command(self, cmdjson, requestid):
|
||||||
|
try:
|
||||||
|
ttopic = "iot/p2p/{}/helper1/bumper/helper1/{}/{}/{}/q/{}/{}".format(
|
||||||
|
cmdjson["cmdName"],
|
||||||
|
cmdjson["toId"],
|
||||||
|
cmdjson["toType"],
|
||||||
|
cmdjson["toRes"],
|
||||||
|
requestid,
|
||||||
|
cmdjson["payloadType"],
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await self.Client.publish(
|
||||||
|
ttopic, str(cmdjson["payload"]).encode(), QOS_0
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
helperbotlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
resp = await self.wait_for_resp(requestid)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
helperbotlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
|
||||||
|
class MQTTServer:
|
||||||
|
default_config = {}
|
||||||
|
bumper_users = []
|
||||||
|
bumper_clients = []
|
||||||
|
bumper_bots = []
|
||||||
|
|
||||||
|
async def broker_coro(self):
|
||||||
|
try:
|
||||||
|
broker = hbmqtt.broker.Broker(config=self.default_config)
|
||||||
|
await broker.start()
|
||||||
|
|
||||||
|
except PermissionError as e:
|
||||||
|
if "bind" in e.strerror:
|
||||||
|
mqttserverlog.exception(
|
||||||
|
"Error binding mqttserver, exiting. Try using a different hostname or IP - {}".format(
|
||||||
|
e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
async def active_bot_listing(self):
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
mqttserverlog.debug("connected bots - %s" % self.bumper_bots.get())
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
address,
|
||||||
|
bumper_users=contextvars.ContextVar,
|
||||||
|
bumper_bots=contextvars.ContextVar,
|
||||||
|
bumper_clients=contextvars.ContextVar,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
self.bumper_users = bumper_users
|
||||||
|
self.bumper_bots = bumper_bots
|
||||||
|
self.bumper_clients = bumper_clients
|
||||||
|
self.mqttserverthread = None
|
||||||
|
self.address = address
|
||||||
|
|
||||||
|
# The below adds a plugin to the hbmqtt.broker.plugins without having to futz with setup.py
|
||||||
|
distribution = pkg_resources.Distribution("hbmqtt.broker.plugins")
|
||||||
|
bumper_plugin = pkg_resources.EntryPoint.parse(
|
||||||
|
"bumper = bumper.mqttserver:BumperMQTTServer_Plugin", dist=distribution
|
||||||
|
)
|
||||||
|
distribution._ep_map = {"hbmqtt.broker.plugins": {"bumper": bumper_plugin}}
|
||||||
|
pkg_resources.working_set.add(distribution)
|
||||||
|
|
||||||
|
# Initialize bot server
|
||||||
|
self.default_config = {
|
||||||
|
"listeners": {
|
||||||
|
"default": {"type": "tcp"},
|
||||||
|
"tls1": {
|
||||||
|
"bind": "{}:{}".format(address[0], address[1]),
|
||||||
|
"ssl": "on",
|
||||||
|
"certfile": bumper.server_cert,
|
||||||
|
"keyfile": bumper.server_key,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sys_interval": 10,
|
||||||
|
"auth": {
|
||||||
|
"allow-anonymous": False,
|
||||||
|
"password-file": os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)), "passwd"
|
||||||
|
),
|
||||||
|
"plugins": ["bumper"], # No plugins == no auth
|
||||||
|
},
|
||||||
|
"topic-check": {"enabled": False},
|
||||||
|
"bumper": {
|
||||||
|
"bumper_users": self.bumper_users,
|
||||||
|
"bumper_bots": self.bumper_bots,
|
||||||
|
"bumper_clients": self.bumper_clients,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def run(self, run_async=False):
|
||||||
|
if run_async:
|
||||||
|
sloop = asyncio.new_event_loop()
|
||||||
|
mqttserverlog.debug("Starting MQTTServer Thread: 1")
|
||||||
|
self.mqttserverthread = Thread(
|
||||||
|
name="MQTTServer_Thread", target=self.run_server, args=(sloop,)
|
||||||
|
)
|
||||||
|
self.mqttserverthread.setDaemon(True)
|
||||||
|
self.mqttserverthread.start()
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.run_server()
|
||||||
|
|
||||||
|
def run_server(self, loop):
|
||||||
|
|
||||||
|
logging.info("Starting MQTT Server at {}".format(self.address))
|
||||||
|
print("Starting MQTT Server at {}".format(self.address))
|
||||||
|
try:
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
loop.run_until_complete(self.broker_coro())
|
||||||
|
# loop.run_until_complete(self.active_bot_listing())
|
||||||
|
loop.run_forever()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
|
||||||
|
class BumperMQTTServer_Plugin:
|
||||||
|
def __init__(self, context):
|
||||||
|
self.context = context
|
||||||
|
try:
|
||||||
|
self.bumper_config = self.context.config["bumper"]
|
||||||
|
self.auth_config = self.context.config["auth"]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
self.context.logger.warning(
|
||||||
|
"'bumper' section not found in context configuration"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def authenticate(self, *args, **kwargs):
|
||||||
|
if not self.auth_config:
|
||||||
|
# auth config section not found
|
||||||
|
self.context.logger.warning(
|
||||||
|
"'auth' section not found in context configuration"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
allow_anonymous = self.auth_config.get(
|
||||||
|
"allow-anonymous", True
|
||||||
|
) # allow anonymous by default
|
||||||
|
if allow_anonymous:
|
||||||
|
authenticated = True
|
||||||
|
self.context.logger.debug("Authentication success: config allows anonymous")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
bumper_users = self.bumper_config["bumper_users"].get()
|
||||||
|
bumper_bots = self.bumper_config["bumper_bots"].get()
|
||||||
|
bumper_clients = self.bumper_config["bumper_clients"].get()
|
||||||
|
session = kwargs.get("session", None)
|
||||||
|
username = session.username
|
||||||
|
password = session.password
|
||||||
|
client_id = session.client_id
|
||||||
|
|
||||||
|
didsplit = str(client_id).split("@")
|
||||||
|
# If this isn't a fake user (fuid) then add as a bot
|
||||||
|
if not (
|
||||||
|
str(didsplit[0]).startswith("fuid")
|
||||||
|
or str(didsplit[0]).startswith("helper")
|
||||||
|
):
|
||||||
|
tmpbotdetail = str(didsplit[1]).split("/")
|
||||||
|
bumper.add_bot(
|
||||||
|
username, didsplit[0], tmpbotdetail[0], tmpbotdetail[1]
|
||||||
|
)
|
||||||
|
mqttserverlog.debug(
|
||||||
|
"new bot authenticated SN: {} DID: {}".format(
|
||||||
|
username, didsplit[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
authenticated = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
tmpclientdetail = str(didsplit[1]).split("/")
|
||||||
|
userid = didsplit[0]
|
||||||
|
realm = tmpclientdetail[0]
|
||||||
|
resource = tmpclientdetail[1]
|
||||||
|
|
||||||
|
if userid == "helper1":
|
||||||
|
authenticated = True
|
||||||
|
else:
|
||||||
|
auth = False
|
||||||
|
if bumper.check_authcode(didsplit[0], password):
|
||||||
|
auth = True
|
||||||
|
elif bumper.use_auth == False:
|
||||||
|
auth = True
|
||||||
|
|
||||||
|
if auth:
|
||||||
|
bumper.add_client(userid, realm, resource)
|
||||||
|
mqttserverlog.debug(
|
||||||
|
"client authenticated {}".format(userid)
|
||||||
|
)
|
||||||
|
authenticated = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
authenticated = False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
authenticated = False
|
||||||
|
|
||||||
|
return authenticated
|
||||||
|
|
||||||
|
async def on_broker_client_connected(self, client_id):
|
||||||
|
try:
|
||||||
|
bumper_users = self.bumper_config["bumper_users"].get()
|
||||||
|
bumper_bots = self.bumper_config["bumper_bots"].get()
|
||||||
|
bumper_clients = self.bumper_config["bumper_clients"].get()
|
||||||
|
didsplit = str(client_id).split("@")
|
||||||
|
|
||||||
|
for bot in bumper_bots:
|
||||||
|
if didsplit[0] == bot.did:
|
||||||
|
bot.mqtt_connection = True
|
||||||
|
mqttserverlog.debug("bot connected {}".format(bot.did))
|
||||||
|
self.bumper_config["bumper_bots"].set(bumper_bots)
|
||||||
|
|
||||||
|
for client in bumper_clients:
|
||||||
|
if didsplit[0] == client.userid and client.userid != "helper1":
|
||||||
|
client.mqtt_connection = True
|
||||||
|
# mqttserverlog.info("client connected {}".format(client.userid))
|
||||||
|
self.bumper_config["bumper_clients"].set(bumper_clients)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
async def on_broker_client_disconnected(self, client_id):
|
||||||
|
try:
|
||||||
|
bumper_users = self.bumper_config["bumper_users"].get()
|
||||||
|
bumper_bots = self.bumper_config["bumper_bots"].get()
|
||||||
|
bumper_clients = self.bumper_config["bumper_clients"].get()
|
||||||
|
didsplit = str(client_id).split("@")
|
||||||
|
|
||||||
|
for bot in bumper_bots:
|
||||||
|
if didsplit[0] == bot.did:
|
||||||
|
bot.mqtt_connection = False
|
||||||
|
mqttserverlog.debug("bot disconnected {}".format(bot.did))
|
||||||
|
self.bumper_config["bumper_bots"].set(bumper_bots)
|
||||||
|
|
||||||
|
for client in bumper_clients:
|
||||||
|
if didsplit[0] == client.userid and client.userid != "helper1":
|
||||||
|
client.mqtt_connection = False
|
||||||
|
# mqttserverlog.info("client disconnected {}".format(client.userid))
|
||||||
|
self.bumper_config["bumper_clients"].set(bumper_clients)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
|
@ -1,50 +1,168 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from threading import Thread
|
||||||
import sys, socket, threading, re, time, logging, uuid, xml.etree.ElementTree as ET
|
import sys, socket, threading, re, time, logging, uuid, xml.etree.ElementTree as ET
|
||||||
|
import base64
|
||||||
|
import ssl
|
||||||
|
import contextvars
|
||||||
|
import bumper
|
||||||
|
|
||||||
|
xmppserverlog = logging.getLogger("xmppserver")
|
||||||
|
|
||||||
|
|
||||||
class XMPPServer():
|
class XMPPServer:
|
||||||
server_id = 'bumper'
|
server_id = "bumper"
|
||||||
bot_id = 'bumpy'
|
bot_id = "bumpy"
|
||||||
client_id = None
|
client_id = None
|
||||||
clients = []
|
clients = []
|
||||||
exit_flag = False
|
exit_flag = False
|
||||||
|
|
||||||
def __init__(self, address):
|
def __init__(
|
||||||
try:
|
self,
|
||||||
|
address,
|
||||||
|
bumper_users=contextvars.ContextVar,
|
||||||
|
bumper_bots=contextvars.ContextVar,
|
||||||
|
bumper_clients=contextvars.ContextVar,
|
||||||
|
):
|
||||||
# Initialize bot server
|
# Initialize bot server
|
||||||
|
self.address = address
|
||||||
|
self.bumper_users = bumper_users
|
||||||
|
self.bumper_bots = bumper_bots
|
||||||
|
self.bumper_clients = bumper_clients
|
||||||
|
|
||||||
|
def run(self, run_async=False):
|
||||||
|
if run_async:
|
||||||
|
xmppserverlog.debug("Starting XMPPServer Thread: 1")
|
||||||
|
self.xmppthread = Thread(name="XMPPServer_Thread", target=self.run_server)
|
||||||
|
self.xmppthread.setDaemon(True)
|
||||||
|
self.xmppthread.start()
|
||||||
|
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.run_server()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
def run_server(self):
|
||||||
|
logging.info("Starting XMPP Server at {}".format(self.address))
|
||||||
|
print("Starting XMPP Server at {}".format(self.address))
|
||||||
|
|
||||||
|
# xmppserverlog.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# Set SSL Context
|
||||||
|
self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||||
|
self.ssl_ctx.load_cert_chain(
|
||||||
|
certfile=bumper.server_cert, keyfile=bumper.server_key
|
||||||
|
)
|
||||||
|
|
||||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
self.socket.bind(address)
|
|
||||||
self.socket.listen(1)
|
try:
|
||||||
logging.info('XMPPServer: listening on {}:{}'.format(address[0], address[1]))
|
self.socket.bind(self.address)
|
||||||
|
self.socket.listen(5)
|
||||||
|
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"listening on {}:{}".format(self.address[0], self.address[1])
|
||||||
|
)
|
||||||
while not self.exit_flag:
|
while not self.exit_flag:
|
||||||
logging.info('XMPPServer: awaiting connection')
|
|
||||||
connection, client_address = self.socket.accept()
|
connection, client_address = self.socket.accept()
|
||||||
|
|
||||||
# disconnect any clients with this ip
|
# disconnect any clients with this ip
|
||||||
for client in self.clients:
|
for client in self.clients:
|
||||||
if client.address == client_address[0]:
|
if client.address == client_address[0]:
|
||||||
client.disconnect()
|
xmppserverlog.debug(
|
||||||
|
"disconnecting existing client {} with resource {}".format(
|
||||||
|
client.address, client.clientresource
|
||||||
|
)
|
||||||
|
)
|
||||||
|
client._disconnect()
|
||||||
|
self.remove_client_byip(client.address)
|
||||||
|
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"starting new client with ip {}".format(client_address[0])
|
||||||
|
)
|
||||||
thread_id = uuid.uuid4()
|
thread_id = uuid.uuid4()
|
||||||
client = Client(thread_id, connection, client_address)
|
client = Client(
|
||||||
|
thread_id,
|
||||||
|
connection,
|
||||||
|
client_address,
|
||||||
|
self.bumper_users,
|
||||||
|
self.bumper_bots,
|
||||||
|
self.bumper_clients,
|
||||||
|
)
|
||||||
|
client.setDaemon(True)
|
||||||
client.start()
|
client.start()
|
||||||
self.clients.append(client)
|
self.clients.append(client)
|
||||||
self.socket.close()
|
|
||||||
|
except PermissionError as e:
|
||||||
|
if "bind" in e.strerror:
|
||||||
|
xmppserverlog.exception(
|
||||||
|
"Error binding XMPPServer, exiting. Try using a different hostname or IP - {}".format(
|
||||||
|
e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error('XMPPServer: {}'.format(e))
|
xmppserverlog.exception("{}".format(e))
|
||||||
except KeyboardInterrupt:
|
exit(1)
|
||||||
logging.debug('XMPPServer: Keyboard interrupt')
|
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
connection.shutdown(socket.SHUT_RDWR)
|
||||||
|
connection.close()
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
logging.info('XMPPServer: bye')
|
xmppserverlog.info("disconnecting")
|
||||||
|
|
||||||
|
self.socket.close()
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
logging.info('XMPPServer: waiting for all client threads to exit')
|
try:
|
||||||
|
xmppserverlog.debug("waiting for all client threads to exit")
|
||||||
for client in self.clients:
|
for client in self.clients:
|
||||||
client.disconnect()
|
client._disconnect()
|
||||||
client.join()
|
|
||||||
self.exit_flag = True
|
self.exit_flag = True
|
||||||
logging.info('XMPPServer: shutting down...')
|
xmppserverlog.debug("shutting down")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def remove_client_byip(self, ip):
|
||||||
|
for client in self.clients:
|
||||||
|
if client.address == ip:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"removing client from client list with ip {} and resource {}".format(
|
||||||
|
client.address, client.clientresource
|
||||||
|
)
|
||||||
|
)
|
||||||
|
client._disconnect()
|
||||||
|
self.clients.remove(client)
|
||||||
|
|
||||||
|
def remove_client_byresource(self, resource):
|
||||||
|
for client in self.clients:
|
||||||
|
if str(client.clientresource).lower() == str(resource).lower():
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"removing client from client list with ip {} and resource {}".format(
|
||||||
|
client.address, client.clientresource
|
||||||
|
)
|
||||||
|
)
|
||||||
|
client._disconnect()
|
||||||
|
self.clients.remove(client)
|
||||||
|
|
||||||
|
def remove_client_byuid(self, uid):
|
||||||
|
for client in self.clients:
|
||||||
|
if str(client.uid).lower() == str(uid).lower():
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"removing client from client list with ip {} and resource {}".format(
|
||||||
|
client.address, client.clientresource
|
||||||
|
)
|
||||||
|
)
|
||||||
|
client._disconnect()
|
||||||
|
self.clients.remove(client)
|
||||||
|
|
||||||
|
|
||||||
class Client(threading.Thread):
|
class Client(threading.Thread):
|
||||||
|
|
@ -58,124 +176,598 @@ class Client(threading.Thread):
|
||||||
BOT = 1
|
BOT = 1
|
||||||
CONTROLLER = 2
|
CONTROLLER = 2
|
||||||
|
|
||||||
def __init__(self, thread_id, connection, client_address):
|
def __init__(
|
||||||
|
self,
|
||||||
|
thread_id,
|
||||||
|
connection,
|
||||||
|
client_address,
|
||||||
|
bumper_users=contextvars.ContextVar,
|
||||||
|
bumper_bots=contextvars.ContextVar,
|
||||||
|
bumper_clients=contextvars.ContextVar,
|
||||||
|
):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.id = thread_id
|
self.id = thread_id
|
||||||
|
self.name = "XMPP_Client_{}".format(client_address[0])
|
||||||
self.type = self.UNKNOWN
|
self.type = self.UNKNOWN
|
||||||
self.state = self.IDLE
|
self.state = self.IDLE
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
self.address = client_address[0]
|
self.address = client_address[0]
|
||||||
|
self.clientresource = ""
|
||||||
|
self.uid = ""
|
||||||
|
self.log_sent_message = False # Set to true to log sends
|
||||||
|
self.log_incoming_data = True # Set to true to log sends
|
||||||
|
self.bumper_users = bumper_users
|
||||||
|
self.bumper_bots = bumper_bots
|
||||||
|
self.bumper_clients = bumper_clients
|
||||||
|
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"new client thread init for client with ip {}".format(self.address)
|
||||||
|
)
|
||||||
|
|
||||||
def send(self, command):
|
def send(self, command):
|
||||||
logging.debug('to {}: {}'.format(self.address, command))
|
try:
|
||||||
|
if not self.connection._closed:
|
||||||
|
if self.log_sent_message:
|
||||||
|
xmppserverlog.debug("send {} - {}".format(self.address, command))
|
||||||
self.connection.send(command.encode())
|
self.connection.send(command.encode())
|
||||||
|
|
||||||
def disconnect(self):
|
except OSError as e:
|
||||||
logging.info('{} disconnecting'.format(self.address))
|
xmppserverlog.error("{}".format(e))
|
||||||
|
|
||||||
|
except BrokenPipeError as e:
|
||||||
|
xmppserverlog.error("{}".format(e))
|
||||||
|
# self._set_state('DISCONNECT')
|
||||||
|
|
||||||
|
except ConnectionResetError as e:
|
||||||
|
xmppserverlog.error("{}".format(e))
|
||||||
|
# self._set_state('DISCONNECT')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def _disconnect(self):
|
||||||
|
try:
|
||||||
|
bumper_bots = self.bumper_bots.get()
|
||||||
|
bumper_clients = self.bumper_clients.get()
|
||||||
|
for bot in bumper_bots:
|
||||||
|
if self.uid == bot.did:
|
||||||
|
bot.xmpp_connection = False
|
||||||
|
# xmppserverlog.info("bot disconnected {}".format(bot.did))
|
||||||
|
|
||||||
|
self.bumper_bots.set(bumper_bots)
|
||||||
|
|
||||||
|
for client in bumper_clients:
|
||||||
|
if self.uid == client.userid and client.userid != "helper1":
|
||||||
|
client.xmpp_connection = False
|
||||||
|
# xmppserverlog.info("client disconnected {}".format(client.userid))
|
||||||
|
|
||||||
|
self.bumper_clients.set(bumper_clients)
|
||||||
|
# xmppserverlog.debug('client {} with resource {} disconnecting'.format(self.address, self.clientresource))
|
||||||
self.connection.close()
|
self.connection.close()
|
||||||
self._set_state('DISCONNECT')
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
def _tag_strip_uri(self, tag):
|
def _tag_strip_uri(self, tag):
|
||||||
if tag[0] == '{':
|
try:
|
||||||
uri, ignore, tag = tag[1:].partition('}')
|
if tag[0] == "{":
|
||||||
|
uri, ignore, tag = tag[1:].partition("}")
|
||||||
return tag
|
return tag
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
def _set_state(self, state):
|
def _set_state(self, state):
|
||||||
|
try:
|
||||||
new_state = getattr(Client, state)
|
new_state = getattr(Client, state)
|
||||||
if self.state > new_state:
|
if self.state > new_state:
|
||||||
raise Exception('{} illegal state change {}->{}'.format(self.address, self.state, new_state))
|
raise Exception(
|
||||||
logging.info('{} state: {}'.format(self.address, state))
|
"{} illegal state change {}->{}".format(
|
||||||
|
self.address, self.state, new_state
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
xmppserverlog.debug("{} state: {}".format(self.address, state))
|
||||||
|
|
||||||
self.state = new_state
|
self.state = new_state
|
||||||
|
|
||||||
|
if new_state == 5:
|
||||||
|
self._disconnect()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
def _handle_ctl(self, xml, data):
|
def _handle_ctl(self, xml, data):
|
||||||
ctl = xml[0][0]
|
try:
|
||||||
if ctl.get('admin') and self.type == self.BOT:
|
|
||||||
logging.info('admin username received from bot: {}'.format(ctl.get('admin')))
|
if data.decode("utf-8").find("roster") > -1:
|
||||||
XMPPServer.client_id = ctl.get('admin')
|
# Return not-implemented for roster
|
||||||
|
self.send(
|
||||||
|
'<iq type="error" id="{}"><error type="cancel" code="501"><feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>'.format(
|
||||||
|
xml.get("id")
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if xml.get("type") == "set":
|
||||||
|
if (
|
||||||
|
data.decode("utf-8").find("com:sf") > -1
|
||||||
|
and xml.get("to") == "rl.ecorobot.net"
|
||||||
|
): # Android bind? Not sure what this does yet.
|
||||||
|
self.send(
|
||||||
|
'<iq id="{}" to="{}@{}/{}" from="rl.ecorobot.net" type="result"/>'.format(
|
||||||
|
xml.get("id"),
|
||||||
|
self.uid,
|
||||||
|
XMPPServer.bot_id,
|
||||||
|
self.clientresource,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"Unknown set type: {}".format(data.decode("utf-8"))
|
||||||
|
)
|
||||||
|
|
||||||
|
if xml[0][0]:
|
||||||
|
ctl = xml[0][0]
|
||||||
|
if ctl.get("admin") and self.type == self.BOT:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"admin username received from bot: {}".format(ctl.get("admin"))
|
||||||
|
)
|
||||||
|
XMPPServer.client_id = ctl.get("admin")
|
||||||
|
return
|
||||||
|
|
||||||
# forward
|
# forward
|
||||||
for client in XMPPServer.clients:
|
for client in XMPPServer.clients:
|
||||||
if client.address != self.address and client.state == client.READY:
|
if client.address != self.address and client.state == client.READY:
|
||||||
if client.type == self.BOT:
|
if client.type == self.BOT:
|
||||||
data = data.decode('utf-8')
|
data = data.decode("utf-8")
|
||||||
id_index = data.find('id')
|
id_index = data.find("id")
|
||||||
if id_index > -1:
|
if id_index > -1:
|
||||||
data = data[:id_index] + 'from="' + XMPPServer.client_id + '" ' + data[id_index:]
|
data = (
|
||||||
|
data[:id_index]
|
||||||
|
+ 'from="'
|
||||||
|
+ XMPPServer.client_id
|
||||||
|
+ '" '
|
||||||
|
+ data[id_index:]
|
||||||
|
)
|
||||||
data = data.encode()
|
data = data.encode()
|
||||||
client.send(data.decode('utf-8'))
|
client.send(data.decode("utf-8"))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
def _handle_ping(self, xml, data):
|
def _handle_ping(self, xml, data):
|
||||||
if(xml.get('to').find('@') == -1):
|
try:
|
||||||
|
if xml.get("to").find("@") == -1:
|
||||||
# Ping to server - respond
|
# Ping to server - respond
|
||||||
self.send('<iq type="result" id="{}" from="{}" />'.format(xml.get('id'), xml.get('to')))
|
self.send(
|
||||||
|
'<iq type="result" id="{}" from="{}" />'.format(
|
||||||
|
xml.get("id"), xml.get("to")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for client in XMPPServer.clients:
|
for client in XMPPServer.clients:
|
||||||
if client.address != self.address and client.state == client.READY:
|
if client.address != self.address and client.state == client.READY:
|
||||||
client.send(data.decode('utf-8'))
|
client.send(data.decode("utf-8"))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
def _handle_result(self, data):
|
def _handle_result(self, data):
|
||||||
# forward
|
# forward
|
||||||
|
try:
|
||||||
for client in XMPPServer.clients:
|
for client in XMPPServer.clients:
|
||||||
if client.address != self.address and client.state == client.READY:
|
if client.address != self.address and client.state == client.READY:
|
||||||
client.send(data.decode('utf-8'))
|
client.send(data.decode("utf-8"))
|
||||||
|
|
||||||
def run(self):
|
except Exception as e:
|
||||||
try:
|
xmppserverlog.exception("{}".format(e))
|
||||||
logging.info('client connected: {}'.format(self.address))
|
|
||||||
self._set_state('CONNECT')
|
def _handle_connect(self, data):
|
||||||
while True:
|
|
||||||
data = self.connection.recv(4096)
|
|
||||||
if data:
|
|
||||||
logging.debug('from {}: {}'.format(self.address, data.decode('utf-8')))
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if self.state == self.CONNECT:
|
if self.state == self.CONNECT:
|
||||||
if data.decode('utf-8').find('jabber:client') > -1:
|
# Client first connecting, send our features
|
||||||
self._set_state('INIT')
|
|
||||||
|
if data.decode("utf-8").find("jabber:client") > -1:
|
||||||
# ack jabbr:client
|
# ack jabbr:client
|
||||||
self.send('<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="{}">'.format(XMPPServer.server_id))
|
# no STARTTLS
|
||||||
time.sleep(0.5)
|
self.send(
|
||||||
|
'<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="{}">'.format(
|
||||||
|
XMPPServer.server_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# with STARTTLS
|
||||||
|
# self.send('<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns:tls="http://www.ietf.org/rfc/rfc2595.txt" xmlns="jabber:client" version="1.0" id="1" from="{}">'.format(XMPPServer.server_id))
|
||||||
|
time.sleep(0.25)
|
||||||
|
# send authentication support for iq-auth (fallback) and SASL
|
||||||
|
self.send(
|
||||||
|
'<stream:features><auth xmlns="http://jabber.org/features/iq-auth"/><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
|
||||||
|
)
|
||||||
|
# self.send('<stream:features><auth xmlns="http://jabber.org/features/iq-auth"/></stream:features>')
|
||||||
|
|
||||||
|
elif data.decode("utf-8").find("jabber:iq:auth") > -1: # Handle iq-auth
|
||||||
|
self._handle_iq_auth(data)
|
||||||
|
|
||||||
|
elif (
|
||||||
|
data.decode("utf-8").find("urn:ietf:params:xml:ns:xmpp-sasl") > -1
|
||||||
|
): # Handle SASL auth
|
||||||
|
self._handle_sasl_auth(data)
|
||||||
|
|
||||||
|
elif self.state == self.INIT:
|
||||||
|
# Client getting session after authentication
|
||||||
|
if data.decode("utf-8").find("jabber:client") > -1:
|
||||||
|
# ack jabbr:client
|
||||||
|
self.send(
|
||||||
|
'<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="{}">'.format(
|
||||||
|
XMPPServer.server_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
time.sleep(0.25)
|
||||||
# session
|
# session
|
||||||
self.send('<stream:features><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>')
|
self.send(
|
||||||
continue
|
'<stream:features><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>'
|
||||||
xml = ET.fromstring(data)
|
)
|
||||||
|
|
||||||
|
else: # Handle init bind
|
||||||
|
xml = ET.fromstring(data.decode("utf-8"))
|
||||||
if len(xml):
|
if len(xml):
|
||||||
child = self._tag_strip_uri(xml[0].tag)
|
child = self._tag_strip_uri(xml[0].tag)
|
||||||
else:
|
else:
|
||||||
child = None
|
child = None
|
||||||
if xml.tag == 'iq':
|
|
||||||
res = None
|
if xml.tag == "iq":
|
||||||
if child == 'bind':
|
if child == "bind":
|
||||||
res = '<iq type="result" id="{}"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>{}</jid></bind></iq>'.format(xml.get('id'), XMPPServer.bot_id)
|
self._handle_bind(xml)
|
||||||
self._set_state('BIND')
|
|
||||||
elif child == 'session':
|
except Exception as e:
|
||||||
res = '<iq type="result" id="{}" />'.format(xml.get('id'))
|
xmppserverlog.exception("{}".format(e))
|
||||||
self._set_state('READY')
|
|
||||||
elif child == 'query':
|
def _handle_iq_auth(self, data):
|
||||||
self._handle_ctl(xml, data)
|
try:
|
||||||
elif child == 'ping':
|
xml = ET.fromstring(data.decode("utf-8"))
|
||||||
self._handle_ping(xml, data)
|
ctl = xml[0][0]
|
||||||
elif xml.get('type') == 'result':
|
xmppserverlog.info("IQ AUTH XML: {}".format(xml))
|
||||||
self._handle_result(data)
|
# Received username and auth tag, send username/password requirement
|
||||||
if res:
|
if (
|
||||||
|
xml.get("type") == "get"
|
||||||
|
and "auth}username" in ctl.tag
|
||||||
|
and self.type == self.UNKNOWN
|
||||||
|
):
|
||||||
|
self.send(
|
||||||
|
'<iq type="result" id="{}"><query xmlns="jabber:iq:auth"><username/><password/></query></iq>'.format(
|
||||||
|
xml.get("id")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Received username, password, resource - Handle auth here and return pass or fail
|
||||||
|
if (
|
||||||
|
xml.get("type") == "set"
|
||||||
|
and "auth}username" in ctl.tag
|
||||||
|
and self.type == self.UNKNOWN
|
||||||
|
):
|
||||||
|
xmlauth = xml[0].getchildren()
|
||||||
|
uid = ""
|
||||||
|
password = ""
|
||||||
|
resource = ""
|
||||||
|
for aitem in xmlauth:
|
||||||
|
if "username" in aitem.tag:
|
||||||
|
self.uid = aitem.text
|
||||||
|
|
||||||
|
elif "password" in aitem.tag:
|
||||||
|
password = aitem.text.split("/")[2]
|
||||||
|
authcode = password
|
||||||
|
|
||||||
|
elif "resource" in aitem.tag:
|
||||||
|
self.clientresource = aitem.text
|
||||||
|
resource = self.clientresource
|
||||||
|
|
||||||
|
if not self.uid.startswith("fuid"):
|
||||||
|
|
||||||
|
# Need sample data to see details here
|
||||||
|
bumper.add_bot("", self.uid, "", resource)
|
||||||
|
xmppserverlog.info("bot authenticated {}".format(self.uid))
|
||||||
|
|
||||||
|
# Client authenticated, move to next state
|
||||||
|
self._set_state("INIT")
|
||||||
|
|
||||||
|
# Successful auth
|
||||||
|
self.send('<iq type="result" id="{}"/>'.format(xml.get("id")))
|
||||||
|
|
||||||
|
else:
|
||||||
|
auth = False
|
||||||
|
if bumper.check_authcode(self.uid, authcode):
|
||||||
|
auth = True
|
||||||
|
elif bumper.use_auth == False:
|
||||||
|
auth = True
|
||||||
|
|
||||||
|
if auth:
|
||||||
|
bumper.add_client(self.uid, "bumper", self.clientresource)
|
||||||
|
xmppserverlog.debug("client authenticated {}".format(self.uid))
|
||||||
|
|
||||||
|
# Client authenticated, move to next state
|
||||||
|
self._set_state("INIT")
|
||||||
|
|
||||||
|
# Successful auth
|
||||||
|
self.send('<iq type="result" id="{}"/>'.format(xml.get("id")))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Failed auth
|
||||||
|
self.send(
|
||||||
|
'<iq type="error" id="{}"><error code="401" type="auth"><not-authorized xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>'.format(
|
||||||
|
xml.get("id")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
except ET.ParseError as e:
|
||||||
|
if "no element found" in e.msg:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"xml parse error - {} - {} - this is common with ecovac protocol".format(
|
||||||
|
data.decode("utf-8"), e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif "not well-formed (invalid token)" in e.msg:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def _handle_sasl_auth(self, data):
|
||||||
|
try:
|
||||||
|
xml = ET.fromstring(data.decode("utf-8"))
|
||||||
|
saslauth = base64.b64decode(xml.text).decode("utf-8").split("/")
|
||||||
|
username = saslauth[0]
|
||||||
|
username = saslauth[0].split("\x00")[1]
|
||||||
|
self.uid = username
|
||||||
|
resource = saslauth[1]
|
||||||
|
self.clientresource = resource
|
||||||
|
authcode = saslauth[2]
|
||||||
|
|
||||||
|
if not self.uid.startswith("fuid"):
|
||||||
|
# Need sample data to see details here
|
||||||
|
bumper.add_bot("", self.uid, "", resource)
|
||||||
|
xmppserverlog.info("bot authenticated {}".format(self.uid))
|
||||||
|
# Send response
|
||||||
|
self.send(
|
||||||
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
|
||||||
|
) # Success
|
||||||
|
|
||||||
|
# Client authenticated, move to next state
|
||||||
|
self._set_state("INIT")
|
||||||
|
|
||||||
|
else:
|
||||||
|
auth = False
|
||||||
|
if bumper.check_authcode(self.uid, authcode):
|
||||||
|
auth = True
|
||||||
|
elif bumper.use_auth == False:
|
||||||
|
auth = True
|
||||||
|
|
||||||
|
if auth:
|
||||||
|
bumper.add_client(self.uid, "bumper", self.clientresource)
|
||||||
|
xmppserverlog.debug("client authenticated {}".format(self.uid))
|
||||||
|
|
||||||
|
# Client authenticated, move to next state
|
||||||
|
self._set_state("INIT")
|
||||||
|
|
||||||
|
# Send response
|
||||||
|
self.send(
|
||||||
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
|
||||||
|
) # Success
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Failed to authenticate
|
||||||
|
self.send(
|
||||||
|
'<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
|
||||||
|
) # Fail
|
||||||
|
|
||||||
|
except ET.ParseError as e:
|
||||||
|
if "no element found" in e.msg:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"xml parse error - {} - {} - this is common with ecovac protocol".format(
|
||||||
|
data.decode("utf-8"), e
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif "not well-formed (invalid token)" in e.msg:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def _handle_bind(self, xml):
|
||||||
|
try:
|
||||||
|
bumper_bots = self.bumper_bots.get()
|
||||||
|
bumper_clients = self.bumper_clients.get()
|
||||||
|
|
||||||
|
for bot in bumper_bots:
|
||||||
|
if self.uid == bot.did:
|
||||||
|
bot.xmpp_connection = True
|
||||||
|
# xmppserverlog.info("bot connected {}".format(bot.did))
|
||||||
|
self.bumper_bots.set(bumper_bots)
|
||||||
|
|
||||||
|
for client in bumper_clients:
|
||||||
|
if self.uid == client.userid:
|
||||||
|
client.xmpp_connection = True
|
||||||
|
# xmppserverlog.info("client connected {}".format(client.userid))
|
||||||
|
self.bumper_clients.set(bumper_clients)
|
||||||
|
|
||||||
|
clientbindxml = xml.getchildren()
|
||||||
|
clientresourcexml = clientbindxml[0].getchildren()
|
||||||
|
if len(clientresourcexml) > 0:
|
||||||
|
self.clientresource = clientresourcexml[0].text
|
||||||
|
self.name = "XMPP_Client_{}".format(self.clientresource)
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"new client {} using resource {}".format(
|
||||||
|
self.address, self.clientresource
|
||||||
|
)
|
||||||
|
)
|
||||||
|
res = '<iq type="result" id="{}"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>{}@{}/{}</jid></bind></iq>'.format(
|
||||||
|
xml.get("id"), self.uid, XMPPServer.bot_id, self.clientresource
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
xmppserverlog.debug("new client {}".format(self.address))
|
||||||
|
res = '<iq type="result" id="{}"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>{}@{}</jid></bind></iq>'.format(
|
||||||
|
xml.get("id"), self.uid, XMPPServer.bot_id
|
||||||
|
)
|
||||||
|
|
||||||
|
self._set_state("BIND")
|
||||||
self.send(res)
|
self.send(res)
|
||||||
elif xml.tag == 'presence':
|
|
||||||
if len(xml) and xml[0].tag == 'status':
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def _handle_session(self, xml):
|
||||||
|
try:
|
||||||
|
res = '<iq type="result" id="{}" />'.format(xml.get("id"))
|
||||||
|
self._set_state("READY")
|
||||||
|
self.send(res)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def _handle_presence(self, xml):
|
||||||
|
try:
|
||||||
|
if len(xml) and xml[0].tag == "status":
|
||||||
# bot announcing arrival
|
# bot announcing arrival
|
||||||
self.type = self.BOT
|
self.type = self.BOT
|
||||||
logging.info('{} type set to BOT (based on presence tag)'.format(self.address))
|
xmppserverlog.debug(
|
||||||
|
"{} type set to BOT (based on presence tag)".format(self.address)
|
||||||
|
)
|
||||||
# send a command from an unknown user - the response will contain the correct admin username
|
# send a command from an unknown user - the response will contain the correct admin username
|
||||||
self.send('<iq type="set" id="{}" from="{}" to="{}"><query xmlns="com:ctl"><ctl td="GetCleanState" /></query></iq>'.format(uuid.uuid4(), 'unknown@ecouser.net', XMPPServer.bot_id))
|
|
||||||
elif xml.get('type') == 'available':
|
self.send(
|
||||||
|
'<iq type="set" id="{}" from="{}" to="{}"><query xmlns="com:ctl"><ctl td="GetCleanState" /></query></iq>'.format(
|
||||||
|
uuid.uuid4(), "unknown@ecouser.net", XMPPServer.bot_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
self.type = self.CONTROLLER
|
self.type = self.CONTROLLER
|
||||||
logging.info('{} type set to CONTROLLER (based on presence tag)'.format(self.address))
|
xmppserverlog.debug(
|
||||||
|
"{} type set to CONTROLLER (based on presence tag)".format(
|
||||||
|
self.address
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.send(
|
||||||
|
'<presence to="{}@{}/{}"> dummy </presence>'.format(
|
||||||
|
self.uid, XMPPServer.bot_id, self.clientresource
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
def _parse_data(self, data):
|
||||||
|
if self.log_incoming_data:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"from {} - {}".format(self.address, data.decode("utf-8"))
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
xml = ET.fromstring(data.decode("utf-8"))
|
||||||
|
self._handle_xml(xml, data)
|
||||||
|
|
||||||
except ET.ParseError as e:
|
except ET.ParseError as e:
|
||||||
logging.debug('error: {}'.format(e))
|
if (
|
||||||
|
"no element found" in e.msg
|
||||||
|
): # Element not closed or not all bytes received
|
||||||
|
# Happens wth connect stream often
|
||||||
|
if "<stream:stream " in data.decode("utf-8"):
|
||||||
|
if self.state == self.CONNECT or self.state == self.INIT:
|
||||||
|
self._handle_connect(data)
|
||||||
|
else:
|
||||||
|
if not (data.decode("utf-8") == "" or data.decode("utf-8") == " "):
|
||||||
|
xmppserverlog.error(
|
||||||
|
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif "not well-formed (invalid token)" in e.msg:
|
||||||
|
# If a lone </stream:stream> - client is signalling end of session/disconnect
|
||||||
|
if not "</stream:stream>" in data.decode("utf-8"):
|
||||||
|
xmppserverlog.error(
|
||||||
|
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.send("</stream:stream>") # Close stream
|
||||||
|
|
||||||
|
elif (
|
||||||
|
"junk after document element" in e.msg
|
||||||
|
): # More than one xml doc in data
|
||||||
|
# try to split it
|
||||||
|
data0 = data.decode("utf-8")
|
||||||
|
data1 = data0[e.position[1] :]
|
||||||
|
data0 = data0[: e.position[1]]
|
||||||
|
# xmppserverlog.debug('xml parse error - {} - {} - split0: {} - split1: {}'.format(data.decode('utf-8'), e, data0, data1))
|
||||||
|
self._parse_data(data0.encode("utf-8"))
|
||||||
|
self._parse_data(data1.encode("utf-8"))
|
||||||
|
|
||||||
|
else:
|
||||||
|
xmppserverlog.debug(
|
||||||
|
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error('XMPPServer: {}'.format(e))
|
xmppserverlog.exception("{}".format(e))
|
||||||
self._set_state('DISCONNECT')
|
|
||||||
|
def _handle_xml(self, xml, data):
|
||||||
|
try:
|
||||||
|
if self.state == self.CONNECT or self.state == self.INIT:
|
||||||
|
self._handle_connect(data)
|
||||||
|
|
||||||
|
if len(xml):
|
||||||
|
child = self._tag_strip_uri(xml[0].tag)
|
||||||
|
else:
|
||||||
|
child = None
|
||||||
|
|
||||||
|
if xml.tag == "iq":
|
||||||
|
if child == "bind":
|
||||||
|
self._handle_bind(xml)
|
||||||
|
elif child == "session":
|
||||||
|
self._handle_session(xml)
|
||||||
|
elif child == "query":
|
||||||
|
self._handle_ctl(xml, data)
|
||||||
|
elif child == "ping":
|
||||||
|
self._handle_ping(xml, data)
|
||||||
|
elif xml.get("type") == "result":
|
||||||
|
self._handle_result(data)
|
||||||
|
elif xml.tag == "presence":
|
||||||
|
self._handle_presence(xml)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error('XMPPServer: {}'.format(e))
|
xmppserverlog.exception("{}".format(e))
|
||||||
self._set_state('DISCONNECT')
|
|
||||||
finally:
|
def run(self):
|
||||||
self.disconnect()
|
# xmppserverlog.info('client connected - {}'.format(self.address))
|
||||||
|
self._set_state("CONNECT")
|
||||||
|
|
||||||
|
while not self.state == self.DISCONNECT and not self.connection._closed:
|
||||||
|
data = b""
|
||||||
|
|
||||||
|
time.sleep(0.2)
|
||||||
|
if not self.connection._closed:
|
||||||
|
try:
|
||||||
|
data = self.connection.recv(4096)
|
||||||
|
|
||||||
|
except ConnectionResetError as e:
|
||||||
|
xmppserverlog.error("{}".format(e))
|
||||||
|
except OSError as e:
|
||||||
|
xmppserverlog.error("{}".format(e))
|
||||||
|
except Exception as e:
|
||||||
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
|
if data != b"":
|
||||||
|
self._parse_data(data)
|
||||||
|
|
||||||
|
|
|
||||||
BIN
certs/CA/cacert.crt
Normal file
BIN
certs/CA/cacert.crt
Normal file
Binary file not shown.
85
certs/CA/cacert.pem
Normal file
85
certs/CA/cacert.pem
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 11078087720677522868 (0x99bd46167599bdb4)
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
Issuer: C=US, ST=California, O=Bumper, OU=Bumper, CN=Bumper
|
||||||
|
Validity
|
||||||
|
Not Before: Feb 1 05:59:32 2019 GMT
|
||||||
|
Not After : Jan 29 05:59:32 2029 GMT
|
||||||
|
Subject: C=US, ST=California, O=Bumper, OU=Bumper, CN=Bumper
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (2048 bit)
|
||||||
|
Modulus:
|
||||||
|
00:aa:6e:af:15:99:3b:09:32:a1:a7:4b:ee:f7:13:
|
||||||
|
68:c1:94:4f:09:71:54:99:02:2d:27:c1:5f:16:2e:
|
||||||
|
45:d3:a6:44:24:fb:bd:f0:9b:a9:f6:6c:49:6f:09:
|
||||||
|
13:76:d6:5b:88:c2:fc:8b:8b:14:4c:39:df:8a:b5:
|
||||||
|
51:a8:83:c1:73:ca:aa:f1:b7:b4:38:e1:f0:91:35:
|
||||||
|
be:6f:c6:99:7c:e1:ef:d9:c0:ee:d5:fb:f8:f5:0f:
|
||||||
|
a7:96:b3:78:95:d6:2a:04:7d:3b:31:42:b6:33:c0:
|
||||||
|
36:2c:06:f1:6d:db:11:99:16:3e:77:c6:c6:df:ed:
|
||||||
|
95:40:b8:39:a2:82:40:fe:bb:f6:8b:63:0c:c6:96:
|
||||||
|
ae:fd:3b:c7:79:a4:f5:5f:4e:83:87:27:33:bb:6f:
|
||||||
|
cf:43:86:55:73:6c:0c:28:61:89:66:72:b3:90:3d:
|
||||||
|
d8:54:53:6a:3a:57:76:01:05:58:ac:78:3d:0d:0a:
|
||||||
|
52:b4:b7:be:2b:4e:9a:6a:f4:f8:a2:3f:21:99:b6:
|
||||||
|
b4:74:fb:d5:c0:23:7b:e8:51:f5:0a:ec:6a:11:4e:
|
||||||
|
9b:c3:f2:56:00:06:3b:58:e3:cf:67:b6:c3:26:50:
|
||||||
|
0e:f7:72:11:3f:39:2c:c9:b7:d5:69:1a:33:dc:8f:
|
||||||
|
81:66:c5:88:3f:c7:01:94:f0:3d:ac:aa:f3:a0:2a:
|
||||||
|
e8:c7
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
5E:39:C7:78:31:83:30:51:82:D9:88:9B:73:0B:72:EF:44:74:7B:D5
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:5E:39:C7:78:31:83:30:51:82:D9:88:9B:73:0B:72:EF:44:74:7B:D5
|
||||||
|
DirName:/C=US/ST=California/O=Bumper/OU=Bumper/CN=Bumper
|
||||||
|
serial:99:BD:46:16:75:99:BD:B4
|
||||||
|
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:TRUE
|
||||||
|
X509v3 Subject Alternative Name:
|
||||||
|
email:null@null.com
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
5e:45:c7:34:7f:9b:a4:6b:cc:3c:e7:34:84:76:ee:ec:98:be:
|
||||||
|
0f:de:ab:b2:a5:6d:59:18:eb:20:de:a4:fc:2c:4d:e4:9a:9c:
|
||||||
|
63:99:be:16:6a:1d:c0:57:73:e8:7f:9e:78:4d:2a:e6:11:a3:
|
||||||
|
b6:57:18:84:6e:ae:a7:97:08:7e:1f:4d:33:fc:46:06:a7:d1:
|
||||||
|
6e:3a:d1:d9:ef:ba:fd:87:b8:e0:ea:51:85:d2:a6:de:49:e1:
|
||||||
|
a6:43:d1:26:15:e2:f9:8b:8c:f0:94:fe:a3:62:dd:cc:ab:be:
|
||||||
|
a5:dc:90:d8:28:0c:85:4b:fb:df:4a:f1:ac:35:86:7f:63:fd:
|
||||||
|
ca:c6:aa:29:0b:b0:e1:e3:08:e6:4c:71:d2:08:32:c9:09:22:
|
||||||
|
6c:61:ec:87:49:8d:77:48:7a:f7:75:86:bb:4e:17:b0:fe:43:
|
||||||
|
4d:2f:95:bd:d5:0f:c1:ef:ba:f7:3e:e0:b6:2f:fa:4a:c9:45:
|
||||||
|
36:97:56:a4:6d:af:86:88:ff:98:2f:b2:e4:04:4d:c4:02:20:
|
||||||
|
fc:66:8c:d5:2b:e9:5a:6c:1f:d2:67:34:c3:39:7e:f4:c7:37:
|
||||||
|
53:6b:11:99:a8:29:8b:3f:f6:3a:82:ae:f5:2c:d5:b7:23:88:
|
||||||
|
bb:03:84:ad:cb:d0:f6:8d:24:9f:54:08:d1:39:da:1a:a9:f6:
|
||||||
|
1b:c9:53:65
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEADCCAuigAwIBAgIJAJm9RhZ1mb20MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV
|
||||||
|
BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQ8wDQYDVQQKEwZCdW1wZXIxDzAN
|
||||||
|
BgNVBAsTBkJ1bXBlcjEPMA0GA1UEAxMGQnVtcGVyMB4XDTE5MDIwMTA1NTkzMloX
|
||||||
|
DTI5MDEyOTA1NTkzMlowVTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju
|
||||||
|
aWExDzANBgNVBAoTBkJ1bXBlcjEPMA0GA1UECxMGQnVtcGVyMQ8wDQYDVQQDEwZC
|
||||||
|
dW1wZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqbq8VmTsJMqGn
|
||||||
|
S+73E2jBlE8JcVSZAi0nwV8WLkXTpkQk+73wm6n2bElvCRN21luIwvyLixRMOd+K
|
||||||
|
tVGog8Fzyqrxt7Q44fCRNb5vxpl84e/ZwO7V+/j1D6eWs3iV1ioEfTsxQrYzwDYs
|
||||||
|
BvFt2xGZFj53xsbf7ZVAuDmigkD+u/aLYwzGlq79O8d5pPVfToOHJzO7b89DhlVz
|
||||||
|
bAwoYYlmcrOQPdhUU2o6V3YBBViseD0NClK0t74rTppq9PiiPyGZtrR0+9XAI3vo
|
||||||
|
UfUK7GoRTpvD8lYABjtY489ntsMmUA73chE/OSzJt9VpGjPcj4FmxYg/xwGU8D2s
|
||||||
|
qvOgKujHAgMBAAGjgdIwgc8wHQYDVR0OBBYEFF45x3gxgzBRgtmIm3MLcu9EdHvV
|
||||||
|
MIGFBgNVHSMEfjB8gBReOcd4MYMwUYLZiJtzC3LvRHR71aFZpFcwVTELMAkGA1UE
|
||||||
|
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDzANBgNVBAoTBkJ1bXBlcjEPMA0G
|
||||||
|
A1UECxMGQnVtcGVyMQ8wDQYDVQQDEwZCdW1wZXKCCQCZvUYWdZm9tDAMBgNVHRME
|
||||||
|
BTADAQH/MBgGA1UdEQQRMA+BDW51bGxAbnVsbC5jb20wDQYJKoZIhvcNAQEFBQAD
|
||||||
|
ggEBAF5FxzR/m6RrzDznNIR27uyYvg/eq7KlbVkY6yDepPwsTeSanGOZvhZqHcBX
|
||||||
|
c+h/nnhNKuYRo7ZXGIRurqeXCH4fTTP8Rgan0W460dnvuv2HuODqUYXSpt5J4aZD
|
||||||
|
0SYV4vmLjPCU/qNi3cyrvqXckNgoDIVL+99K8aw1hn9j/crGqikLsOHjCOZMcdII
|
||||||
|
MskJImxh7IdJjXdIevd1hrtOF7D+Q00vlb3VD8Hvuvc+4LYv+krJRTaXVqRtr4aI
|
||||||
|
/5gvsuQETcQCIPxmjNUr6VpsH9JnNMM5fvTHN1NrEZmoKYs/9jqCrvUs1bcjiLsD
|
||||||
|
hK3L0PaNJJ9UCNE52hqp9hvJU2U=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
18
certs/CA/careq.pem
Normal file
18
certs/CA/careq.pem
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIC2TCCAcECAQAwaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx
|
||||||
|
EjAQBgNVBAcTCUhhd3Rob3JuZTEPMA0GA1UEChMGQnVtcGVyMQ8wDQYDVQQLEwZC
|
||||||
|
dW1wZXIxDzANBgNVBAMTBkJ1bXBlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
||||||
|
AQoCggEBAKpurxWZOwkyoadL7vcTaMGUTwlxVJkCLSfBXxYuRdOmRCT7vfCbqfZs
|
||||||
|
SW8JE3bWW4jC/IuLFEw534q1UaiDwXPKqvG3tDjh8JE1vm/GmXzh79nA7tX7+PUP
|
||||||
|
p5azeJXWKgR9OzFCtjPANiwG8W3bEZkWPnfGxt/tlUC4OaKCQP679otjDMaWrv07
|
||||||
|
x3mk9V9Og4cnM7tvz0OGVXNsDChhiWZys5A92FRTajpXdgEFWKx4PQ0KUrS3vitO
|
||||||
|
mmr0+KI/IZm2tHT71cAje+hR9QrsahFOm8PyVgAGO1jjz2e2wyZQDvdyET85LMm3
|
||||||
|
1WkaM9yPgWbFiD/HAZTwPayq86Aq6McCAwEAAaArMCkGCSqGSIb3DQEJDjEcMBow
|
||||||
|
GAYDVR0RBBEwD4ENbnVsbEBudWxsLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAQU/v
|
||||||
|
8GRj5M3Qs0at2UGxz7uKZF5RKt6hE5WreS/joaMkK4+kTqA8FsfCGz0zUyyLWNKh
|
||||||
|
1o4b99yvYXG+rP8euMyBql/WSnEcF0KYrrGyUjH0qQQfTHqC0UqolvqoQCkPbvUO
|
||||||
|
sWZVzHeVxIH3nNtXgkIS+PHvTIhDLA3CxrDSe5jz4XLaACPoK/dawojAd0Qet0zZ
|
||||||
|
GfXk9Vm/5D0k/7MPkstOTAFCvfln2cdoYhA34WNkPW+uZNGBEdXTN3Tg/Oj8qAqV
|
||||||
|
RdIXR8joIzgAJnCit1IDyTPHR88UNTlFtk4AJ9Ott4v1hmUOf3kBqB0owXj+fCpq
|
||||||
|
3Mz7yL1DioePr8h7iw==
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
19
certs/CA/certreqs/bumper_req.txt
Normal file
19
certs/CA/certreqs/bumper_req.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIC0DCCAbgCAQAwETEPMA0GA1UEAwwGQnVtcGVyMIIBIjANBgkqhkiG9w0BAQEF
|
||||||
|
AAOCAQ8AMIIBCgKCAQEA0eEqslnaJcbhqTalVMQTNT8/s6957rLSw78cZSVQEISN
|
||||||
|
MwyS4Q5tYYJzXXEmqyp8rBMWAArfWGaPO8sgKs6rC9UcYurVr1GbxtsVdRRRI/fg
|
||||||
|
tt2rtWQYdkBI7Dz9vjkcUF1tWwydWzPtKunzHGq9XNWY8NvJ4eq5dnO1oF1wyPWe
|
||||||
|
5bbKURJpA3gOXuCtEY+TOv++8PK3vdPQnkE1pFRnTfBP5rmatnReFlbfxCUCwcnq
|
||||||
|
X5a0+BeDanw4GSm4m0MCFXGWEb07P+8JUdUP+2rbMIZwUb42BBz/UKBeg410vgwh
|
||||||
|
PISyQetW4XGn3bLJk933GFO6gvOU9CVdzPdxRPPLPQIDAQABoHoweAYJKoZIhvcN
|
||||||
|
AQkOMWswaTAOBgNVHQ8BAf8EBAMCBaAwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEG
|
||||||
|
CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwJwYDVR0RBCAwHoINKi5lY291c2VyLm5l
|
||||||
|
dIINKi5lY292YWNzLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAloHqI4fjQEL0H0I0
|
||||||
|
TMr+DemH5GhYyVC7hs5bPfajqUbrQdTMvfUhfcUm0JgAmicKlAvVe1+75grkXeCF
|
||||||
|
sfGBl10f5dbO9CEHBT9kzDJWhFT/ZFzGlblAwB6Kad7QGpIMKncJCZBUwkE4zrwO
|
||||||
|
wEI1hAo0zvJr/SETte4YO9JianzT1Tu5f3nVgvKYVM+XJ0KZ7ReIdrT0Ev95mZhB
|
||||||
|
tmZ1R7WeYtO8yZXVHP28uOHZcHCKgKcnrpb13yuNd6mkR643xMoirwp9t7xayJAt
|
||||||
|
RnQgvb9o8HXNZoO1whr4ZF8gNs0MQGnT72MoV5E2KSZ9HMO4nQlzj9wIiEMls+ss
|
||||||
|
UTUhBg==
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
|
||||||
85
certs/CA/certsdb/99BD46167599BDB4.pem
Normal file
85
certs/CA/certsdb/99BD46167599BDB4.pem
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 11078087720677522868 (0x99bd46167599bdb4)
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
Issuer: C=US, ST=California, O=Bumper, OU=Bumper, CN=Bumper
|
||||||
|
Validity
|
||||||
|
Not Before: Feb 1 05:59:32 2019 GMT
|
||||||
|
Not After : Jan 29 05:59:32 2029 GMT
|
||||||
|
Subject: C=US, ST=California, O=Bumper, OU=Bumper, CN=Bumper
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (2048 bit)
|
||||||
|
Modulus:
|
||||||
|
00:aa:6e:af:15:99:3b:09:32:a1:a7:4b:ee:f7:13:
|
||||||
|
68:c1:94:4f:09:71:54:99:02:2d:27:c1:5f:16:2e:
|
||||||
|
45:d3:a6:44:24:fb:bd:f0:9b:a9:f6:6c:49:6f:09:
|
||||||
|
13:76:d6:5b:88:c2:fc:8b:8b:14:4c:39:df:8a:b5:
|
||||||
|
51:a8:83:c1:73:ca:aa:f1:b7:b4:38:e1:f0:91:35:
|
||||||
|
be:6f:c6:99:7c:e1:ef:d9:c0:ee:d5:fb:f8:f5:0f:
|
||||||
|
a7:96:b3:78:95:d6:2a:04:7d:3b:31:42:b6:33:c0:
|
||||||
|
36:2c:06:f1:6d:db:11:99:16:3e:77:c6:c6:df:ed:
|
||||||
|
95:40:b8:39:a2:82:40:fe:bb:f6:8b:63:0c:c6:96:
|
||||||
|
ae:fd:3b:c7:79:a4:f5:5f:4e:83:87:27:33:bb:6f:
|
||||||
|
cf:43:86:55:73:6c:0c:28:61:89:66:72:b3:90:3d:
|
||||||
|
d8:54:53:6a:3a:57:76:01:05:58:ac:78:3d:0d:0a:
|
||||||
|
52:b4:b7:be:2b:4e:9a:6a:f4:f8:a2:3f:21:99:b6:
|
||||||
|
b4:74:fb:d5:c0:23:7b:e8:51:f5:0a:ec:6a:11:4e:
|
||||||
|
9b:c3:f2:56:00:06:3b:58:e3:cf:67:b6:c3:26:50:
|
||||||
|
0e:f7:72:11:3f:39:2c:c9:b7:d5:69:1a:33:dc:8f:
|
||||||
|
81:66:c5:88:3f:c7:01:94:f0:3d:ac:aa:f3:a0:2a:
|
||||||
|
e8:c7
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
5E:39:C7:78:31:83:30:51:82:D9:88:9B:73:0B:72:EF:44:74:7B:D5
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:5E:39:C7:78:31:83:30:51:82:D9:88:9B:73:0B:72:EF:44:74:7B:D5
|
||||||
|
DirName:/C=US/ST=California/O=Bumper/OU=Bumper/CN=Bumper
|
||||||
|
serial:99:BD:46:16:75:99:BD:B4
|
||||||
|
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:TRUE
|
||||||
|
X509v3 Subject Alternative Name:
|
||||||
|
email:null@null.com
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
5e:45:c7:34:7f:9b:a4:6b:cc:3c:e7:34:84:76:ee:ec:98:be:
|
||||||
|
0f:de:ab:b2:a5:6d:59:18:eb:20:de:a4:fc:2c:4d:e4:9a:9c:
|
||||||
|
63:99:be:16:6a:1d:c0:57:73:e8:7f:9e:78:4d:2a:e6:11:a3:
|
||||||
|
b6:57:18:84:6e:ae:a7:97:08:7e:1f:4d:33:fc:46:06:a7:d1:
|
||||||
|
6e:3a:d1:d9:ef:ba:fd:87:b8:e0:ea:51:85:d2:a6:de:49:e1:
|
||||||
|
a6:43:d1:26:15:e2:f9:8b:8c:f0:94:fe:a3:62:dd:cc:ab:be:
|
||||||
|
a5:dc:90:d8:28:0c:85:4b:fb:df:4a:f1:ac:35:86:7f:63:fd:
|
||||||
|
ca:c6:aa:29:0b:b0:e1:e3:08:e6:4c:71:d2:08:32:c9:09:22:
|
||||||
|
6c:61:ec:87:49:8d:77:48:7a:f7:75:86:bb:4e:17:b0:fe:43:
|
||||||
|
4d:2f:95:bd:d5:0f:c1:ef:ba:f7:3e:e0:b6:2f:fa:4a:c9:45:
|
||||||
|
36:97:56:a4:6d:af:86:88:ff:98:2f:b2:e4:04:4d:c4:02:20:
|
||||||
|
fc:66:8c:d5:2b:e9:5a:6c:1f:d2:67:34:c3:39:7e:f4:c7:37:
|
||||||
|
53:6b:11:99:a8:29:8b:3f:f6:3a:82:ae:f5:2c:d5:b7:23:88:
|
||||||
|
bb:03:84:ad:cb:d0:f6:8d:24:9f:54:08:d1:39:da:1a:a9:f6:
|
||||||
|
1b:c9:53:65
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEADCCAuigAwIBAgIJAJm9RhZ1mb20MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV
|
||||||
|
BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQ8wDQYDVQQKEwZCdW1wZXIxDzAN
|
||||||
|
BgNVBAsTBkJ1bXBlcjEPMA0GA1UEAxMGQnVtcGVyMB4XDTE5MDIwMTA1NTkzMloX
|
||||||
|
DTI5MDEyOTA1NTkzMlowVTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju
|
||||||
|
aWExDzANBgNVBAoTBkJ1bXBlcjEPMA0GA1UECxMGQnVtcGVyMQ8wDQYDVQQDEwZC
|
||||||
|
dW1wZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqbq8VmTsJMqGn
|
||||||
|
S+73E2jBlE8JcVSZAi0nwV8WLkXTpkQk+73wm6n2bElvCRN21luIwvyLixRMOd+K
|
||||||
|
tVGog8Fzyqrxt7Q44fCRNb5vxpl84e/ZwO7V+/j1D6eWs3iV1ioEfTsxQrYzwDYs
|
||||||
|
BvFt2xGZFj53xsbf7ZVAuDmigkD+u/aLYwzGlq79O8d5pPVfToOHJzO7b89DhlVz
|
||||||
|
bAwoYYlmcrOQPdhUU2o6V3YBBViseD0NClK0t74rTppq9PiiPyGZtrR0+9XAI3vo
|
||||||
|
UfUK7GoRTpvD8lYABjtY489ntsMmUA73chE/OSzJt9VpGjPcj4FmxYg/xwGU8D2s
|
||||||
|
qvOgKujHAgMBAAGjgdIwgc8wHQYDVR0OBBYEFF45x3gxgzBRgtmIm3MLcu9EdHvV
|
||||||
|
MIGFBgNVHSMEfjB8gBReOcd4MYMwUYLZiJtzC3LvRHR71aFZpFcwVTELMAkGA1UE
|
||||||
|
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDzANBgNVBAoTBkJ1bXBlcjEPMA0G
|
||||||
|
A1UECxMGQnVtcGVyMQ8wDQYDVQQDEwZCdW1wZXKCCQCZvUYWdZm9tDAMBgNVHRME
|
||||||
|
BTADAQH/MBgGA1UdEQQRMA+BDW51bGxAbnVsbC5jb20wDQYJKoZIhvcNAQEFBQAD
|
||||||
|
ggEBAF5FxzR/m6RrzDznNIR27uyYvg/eq7KlbVkY6yDepPwsTeSanGOZvhZqHcBX
|
||||||
|
c+h/nnhNKuYRo7ZXGIRurqeXCH4fTTP8Rgan0W460dnvuv2HuODqUYXSpt5J4aZD
|
||||||
|
0SYV4vmLjPCU/qNi3cyrvqXckNgoDIVL+99K8aw1hn9j/crGqikLsOHjCOZMcdII
|
||||||
|
MskJImxh7IdJjXdIevd1hrtOF7D+Q00vlb3VD8Hvuvc+4LYv+krJRTaXVqRtr4aI
|
||||||
|
/5gvsuQETcQCIPxmjNUr6VpsH9JnNMM5fvTHN1NrEZmoKYs/9jqCrvUs1bcjiLsD
|
||||||
|
hK3L0PaNJJ9UCNE52hqp9hvJU2U=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
85
certs/CA/certsdb/99BD46167599BDB5.pem
Normal file
85
certs/CA/certsdb/99BD46167599BDB5.pem
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 11078087720677522869 (0x99bd46167599bdb5)
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
Issuer: C=US, ST=California, O=Bumper, OU=Bumper, CN=Bumper
|
||||||
|
Validity
|
||||||
|
Not Before: Feb 1 06:17:18 2019 GMT
|
||||||
|
Not After : Jan 29 06:17:18 2029 GMT
|
||||||
|
Subject: CN=Bumper
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (2048 bit)
|
||||||
|
Modulus:
|
||||||
|
00:d1:e1:2a:b2:59:da:25:c6:e1:a9:36:a5:54:c4:
|
||||||
|
13:35:3f:3f:b3:af:79:ee:b2:d2:c3:bf:1c:65:25:
|
||||||
|
50:10:84:8d:33:0c:92:e1:0e:6d:61:82:73:5d:71:
|
||||||
|
26:ab:2a:7c:ac:13:16:00:0a:df:58:66:8f:3b:cb:
|
||||||
|
20:2a:ce:ab:0b:d5:1c:62:ea:d5:af:51:9b:c6:db:
|
||||||
|
15:75:14:51:23:f7:e0:b6:dd:ab:b5:64:18:76:40:
|
||||||
|
48:ec:3c:fd:be:39:1c:50:5d:6d:5b:0c:9d:5b:33:
|
||||||
|
ed:2a:e9:f3:1c:6a:bd:5c:d5:98:f0:db:c9:e1:ea:
|
||||||
|
b9:76:73:b5:a0:5d:70:c8:f5:9e:e5:b6:ca:51:12:
|
||||||
|
69:03:78:0e:5e:e0:ad:11:8f:93:3a:ff:be:f0:f2:
|
||||||
|
b7:bd:d3:d0:9e:41:35:a4:54:67:4d:f0:4f:e6:b9:
|
||||||
|
9a:b6:74:5e:16:56:df:c4:25:02:c1:c9:ea:5f:96:
|
||||||
|
b4:f8:17:83:6a:7c:38:19:29:b8:9b:43:02:15:71:
|
||||||
|
96:11:bd:3b:3f:ef:09:51:d5:0f:fb:6a:db:30:86:
|
||||||
|
70:51:be:36:04:1c:ff:50:a0:5e:83:8d:74:be:0c:
|
||||||
|
21:3c:84:b2:41:eb:56:e1:71:a7:dd:b2:c9:93:dd:
|
||||||
|
f7:18:53:ba:82:f3:94:f4:25:5d:cc:f7:71:44:f3:
|
||||||
|
cb:3d
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:FALSE
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
26:7E:4F:14:80:43:9E:EF:64:C2:61:6E:81:68:4B:6A:F2:4E:B5:40
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:5E:39:C7:78:31:83:30:51:82:D9:88:9B:73:0B:72:EF:44:74:7B:D5
|
||||||
|
|
||||||
|
X509v3 Subject Alternative Name:
|
||||||
|
DNS:*.ecouser.net, DNS:*.ecovacs.com
|
||||||
|
X509v3 Key Usage: critical
|
||||||
|
Digital Signature, Key Encipherment
|
||||||
|
X509v3 Extended Key Usage: critical
|
||||||
|
TLS Web Server Authentication, TLS Web Client Authentication
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
89:11:41:18:ac:22:0d:4a:08:31:ee:18:d4:cf:88:02:93:f0:
|
||||||
|
ef:53:f5:86:e1:b2:16:0e:8d:87:f9:e9:29:7a:aa:95:4e:d7:
|
||||||
|
89:96:ac:1c:9a:19:8c:84:62:41:7a:b8:8c:46:07:8f:56:ae:
|
||||||
|
18:31:52:cb:60:3f:01:57:4d:65:d6:5f:95:da:bc:c1:53:35:
|
||||||
|
fa:cc:c6:49:aa:fe:35:d3:05:15:2c:bd:9b:ac:f0:36:d7:71:
|
||||||
|
bc:ed:3e:f9:ac:c8:84:58:5e:f0:02:02:47:9c:0f:f3:f6:06:
|
||||||
|
35:4d:3b:69:2f:bb:81:cd:ae:61:84:75:97:b1:c2:e7:e1:74:
|
||||||
|
18:61:bc:c0:6b:2e:4d:f6:61:0d:36:72:3d:e2:ef:ef:e5:cf:
|
||||||
|
7a:17:af:d6:7c:d2:a4:88:ca:b3:4e:27:7e:f3:da:96:33:ef:
|
||||||
|
12:12:02:5a:31:70:82:e9:b6:e4:a9:57:d7:a4:b3:f6:3f:ea:
|
||||||
|
fa:63:6a:e6:b7:26:73:bb:c1:87:4e:e2:13:40:65:b9:04:3c:
|
||||||
|
c9:95:d7:8e:07:6f:9d:af:d2:36:ee:aa:5d:e5:f9:bb:cc:47:
|
||||||
|
9c:ce:62:cf:28:2e:a3:58:8b:63:a2:e3:c3:f4:b0:69:98:aa:
|
||||||
|
84:ff:a5:c2:52:8b:e8:b4:5c:2c:67:ef:63:b3:5c:42:95:6d:
|
||||||
|
6b:d7:5b:11
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDkzCCAnugAwIBAgIJAJm9RhZ1mb21MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV
|
||||||
|
BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQ8wDQYDVQQKEwZCdW1wZXIxDzAN
|
||||||
|
BgNVBAsTBkJ1bXBlcjEPMA0GA1UEAxMGQnVtcGVyMB4XDTE5MDIwMTA2MTcxOFoX
|
||||||
|
DTI5MDEyOTA2MTcxOFowETEPMA0GA1UEAwwGQnVtcGVyMIIBIjANBgkqhkiG9w0B
|
||||||
|
AQEFAAOCAQ8AMIIBCgKCAQEA0eEqslnaJcbhqTalVMQTNT8/s6957rLSw78cZSVQ
|
||||||
|
EISNMwyS4Q5tYYJzXXEmqyp8rBMWAArfWGaPO8sgKs6rC9UcYurVr1GbxtsVdRRR
|
||||||
|
I/fgtt2rtWQYdkBI7Dz9vjkcUF1tWwydWzPtKunzHGq9XNWY8NvJ4eq5dnO1oF1w
|
||||||
|
yPWe5bbKURJpA3gOXuCtEY+TOv++8PK3vdPQnkE1pFRnTfBP5rmatnReFlbfxCUC
|
||||||
|
wcnqX5a0+BeDanw4GSm4m0MCFXGWEb07P+8JUdUP+2rbMIZwUb42BBz/UKBeg410
|
||||||
|
vgwhPISyQetW4XGn3bLJk933GFO6gvOU9CVdzPdxRPPLPQIDAQABo4GpMIGmMAkG
|
||||||
|
A1UdEwQCMAAwHQYDVR0OBBYEFCZ+TxSAQ57vZMJhboFoS2ryTrVAMB8GA1UdIwQY
|
||||||
|
MBaAFF45x3gxgzBRgtmIm3MLcu9EdHvVMCcGA1UdEQQgMB6CDSouZWNvdXNlci5u
|
||||||
|
ZXSCDSouZWNvdmFjcy5jb20wDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQG
|
||||||
|
CCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAiRFBGKwiDUoI
|
||||||
|
Me4Y1M+IApPw71P1huGyFg6Nh/npKXqqlU7XiZasHJoZjIRiQXq4jEYHj1auGDFS
|
||||||
|
y2A/AVdNZdZfldq8wVM1+szGSar+NdMFFSy9m6zwNtdxvO0++azIhFhe8AICR5wP
|
||||||
|
8/YGNU07aS+7gc2uYYR1l7HC5+F0GGG8wGsuTfZhDTZyPeLv7+XPehev1nzSpIjK
|
||||||
|
s04nfvPaljPvEhICWjFwgum25KlX16Sz9j/q+mNq5rcmc7vBh07iE0BluQQ8yZXX
|
||||||
|
jgdvna/SNu6qXeX5u8xHnM5izyguo1iLY6Ljw/SwaZiqhP+lwlKL6LRcLGfvY7Nc
|
||||||
|
QpVta9dbEQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
2
certs/CA/index.txt
Normal file
2
certs/CA/index.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
V 290129055932Z 99BD46167599BDB4 unknown /C=US/ST=California/O=Bumper/OU=Bumper/CN=Bumper
|
||||||
|
V 290129061718Z 99BD46167599BDB5 unknown /CN=Bumper
|
||||||
1
certs/CA/index.txt.attr
Normal file
1
certs/CA/index.txt.attr
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
unique_subject = yes
|
||||||
1
certs/CA/index.txt.attr.old
Normal file
1
certs/CA/index.txt.attr.old
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
unique_subject = yes
|
||||||
1
certs/CA/index.txt.old
Normal file
1
certs/CA/index.txt.old
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
V 290129055932Z 99BD46167599BDB4 unknown /C=US/ST=California/O=Bumper/OU=Bumper/CN=Bumper
|
||||||
180
certs/CA/openssl.cnf
Normal file
180
certs/CA/openssl.cnf
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
HOME = .
|
||||||
|
RANDFILE = $ENV::HOME/.rnd
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# CA Definition
|
||||||
|
[ ca ]
|
||||||
|
default_ca = CA_default # The default ca section
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Per the above, this is where we define CA values
|
||||||
|
[ CA_default ]
|
||||||
|
|
||||||
|
dir = . # Where everything is kept
|
||||||
|
certs = $dir/certsdb # Where the issued certs are kept
|
||||||
|
new_certs_dir = $certs # default place for new certs.
|
||||||
|
database = $dir/index.txt # database index file.
|
||||||
|
certificate = $dir/cacert.pem # The CA certificate
|
||||||
|
private_key = $dir/private/cakey.pem# The private key
|
||||||
|
serial = $dir/serial # The current serial number
|
||||||
|
RANDFILE = $dir/private/.rand # private random number file
|
||||||
|
|
||||||
|
crldir = $dir/crl
|
||||||
|
crlnumber = $dir/crlnumber # the current crl number
|
||||||
|
crl = $crldir/crl.pem # The current CRL
|
||||||
|
|
||||||
|
# By default we use "user certificate" extensions when signing
|
||||||
|
x509_extensions = usr_cert # The extentions to add to the cert
|
||||||
|
|
||||||
|
# Honor extensions requested of us
|
||||||
|
copy_extensions = copy
|
||||||
|
|
||||||
|
# Comment out the following two lines for the "traditional"
|
||||||
|
# (and highly broken) format.
|
||||||
|
name_opt = ca_default # Subject Name options
|
||||||
|
cert_opt = ca_default # Certificate field options
|
||||||
|
|
||||||
|
# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
|
||||||
|
# so this is commented out by default to leave a V1 CRL.
|
||||||
|
# crlnumber must also be commented out to leave a V1 CRL.
|
||||||
|
#crl_extensions = crl_ext
|
||||||
|
default_days = 3650 # how long to certify for
|
||||||
|
default_crl_days= 30 # how long before next CRL
|
||||||
|
default_md = sha1 # which md to use.
|
||||||
|
preserve = no # keep passed DN ordering
|
||||||
|
|
||||||
|
# A few difference way of specifying how similar the request should look
|
||||||
|
# For type CA, the listed attributes must be the same, and the optional
|
||||||
|
# and supplied fields are just that :-)
|
||||||
|
policy = policy_anything
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# The default policy for the CA when signing requests, requires some
|
||||||
|
# resemblence to the CA cert
|
||||||
|
#
|
||||||
|
[ policy_match ]
|
||||||
|
countryName = match # Must be the same as the CA
|
||||||
|
stateOrProvinceName = match # Must be the same as the CA
|
||||||
|
organizationName = match # Must be the same as the CA
|
||||||
|
organizationalUnitName = optional # not required
|
||||||
|
commonName = supplied # must be there, whatever it is
|
||||||
|
emailAddress = optional # not required
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# An alternative policy not referred to anywhere in this file. Can
|
||||||
|
# be used by specifying '-policy policy_anything' to ca(8).
|
||||||
|
#
|
||||||
|
[ policy_anything ]
|
||||||
|
countryName = optional
|
||||||
|
stateOrProvinceName = optional
|
||||||
|
localityName = optional
|
||||||
|
organizationName = optional
|
||||||
|
organizationalUnitName = optional
|
||||||
|
commonName = supplied
|
||||||
|
emailAddress = optional
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# This is where we define how to generate CSRs
|
||||||
|
[ req ]
|
||||||
|
default_bits = 2048
|
||||||
|
default_keyfile = privkey.pem
|
||||||
|
distinguished_name = req_distinguished_name # where to get DN for reqs
|
||||||
|
attributes = req_attributes # req attributes
|
||||||
|
x509_extensions = v3_ca # The extentions to add to self signed certs
|
||||||
|
req_extensions = v3_req # The extensions to add to req's
|
||||||
|
prompt = no
|
||||||
|
|
||||||
|
# This sets a mask for permitted string types. There are several options.
|
||||||
|
# default: PrintableString, T61String, BMPString.
|
||||||
|
# pkix : PrintableString, BMPString.
|
||||||
|
# utf8only: only UTF8Strings.
|
||||||
|
# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
|
||||||
|
# MASK:XXXX a literal mask value.
|
||||||
|
# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings
|
||||||
|
# so use this option with caution!
|
||||||
|
string_mask = nombstr
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Per "req" section, this is where we define DN info
|
||||||
|
[ req_distinguished_name ]
|
||||||
|
countryName = Country Name (2 letter code)
|
||||||
|
countryName_default = US
|
||||||
|
countryName_min = 2
|
||||||
|
countryName_max = 2
|
||||||
|
|
||||||
|
stateOrProvinceName = State or Province Name (full name)
|
||||||
|
stateOrProvinceName_default = California
|
||||||
|
|
||||||
|
localityName = Locality Name (eg, city)
|
||||||
|
localityName_default = Hawthorne
|
||||||
|
|
||||||
|
0.organizationName = Organization Name (eg, company)
|
||||||
|
0.organizationName_default = PhilNet
|
||||||
|
|
||||||
|
organizationalUnitName = Organizational Unit Name (eg, section)
|
||||||
|
|
||||||
|
commonName = Common Name (eg, YOUR name)
|
||||||
|
commonName_max = 64
|
||||||
|
|
||||||
|
emailAddress = Email Address
|
||||||
|
emailAddress_max = 64
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# We don't want these, but the section must exist
|
||||||
|
[ req_attributes ]
|
||||||
|
#challengePassword = A challenge password
|
||||||
|
#challengePassword_min = 4
|
||||||
|
#challengePassword_max = 20
|
||||||
|
#unstructuredName = An optional company name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
[ v3_req ]
|
||||||
|
keyUsage=critical,digitalSignature,keyEncipherment
|
||||||
|
extendedKeyUsage=critical,serverAuth,clientAuth
|
||||||
|
basicConstraints=critical,CA:false
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
[ alt_names ]
|
||||||
|
DNS.0 = *.ecouser.net
|
||||||
|
DNS.1 = *.ecovacs.com
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Extensions for when we sign normal certs (specified as default)
|
||||||
|
[ usr_cert ]
|
||||||
|
basicConstraints = CA:false
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid,issuer
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
[ alt_names ]
|
||||||
|
DNS.0 = *.ecouser.net
|
||||||
|
DNS.1 = *.ecovacs.com
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Same as above, but cert req already has SubjectAltName
|
||||||
|
[ usr_cert_has_san ]
|
||||||
|
basicConstraints = CA:false
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid,issuer
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
[ alt_names ]
|
||||||
|
DNS.0 = *.ecouser.net
|
||||||
|
DNS.1 = *.ecovacs.com
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Extensions to use when signing a CA
|
||||||
|
[ v3_ca ]
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid:always,issuer:always
|
||||||
|
basicConstraints = CA:true
|
||||||
|
subjectAltName=email:move
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Same as above, but CA req already has SubjectAltName
|
||||||
|
[ v3_ca_has_san ]
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid:always,issuer:always
|
||||||
|
basicConstraints = CA:true
|
||||||
|
|
||||||
30
certs/CA/private/cakey.pem
Normal file
30
certs/CA/private/cakey.pem
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||||
|
MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIzLScD02ODzQCAggA
|
||||||
|
MB0GCWCGSAFlAwQBKgQQzjzZ8LiXuLxb/Rx75kGXkwSCBNAmHUh4RTgy1bJmwZN7
|
||||||
|
uyb4gGHtY9+ZvKZikibq7LpiwmDAwjjwguJz/HRsquq8fsZDpB+rV4hD4lB3lHuj
|
||||||
|
jqETzKst5qLTgP0cC9BS0S0P9f0/L7MEcwtiBkds6dVs4Ot6IDr4napC+6wQCBfr
|
||||||
|
ISrTN8+ls9IVghiS1eOrarFTy2ZxkXFM6PJczLd15/pbTm91etLWl1vnPCJn3MTc
|
||||||
|
vUvneAChKFiwaX4Cbx8bhQP11OBBNshM1zANSoGAdijRpvRCGfeOIvgLsbBl9N0d
|
||||||
|
+lV6iJR0TzOr4GquBDXor/y2Mofn6wYRwtSLRUgHgAxy1USosWVuXwrtGO4htPoR
|
||||||
|
aGt8V3h4XQ90VGAh6ez43/nmkVDgIj8VU9qZ+skCz7Y4TxPZYXUIcnG14KFj9lsk
|
||||||
|
r+uV6HmHyBKSlSkAE+D9uukQb71oIW83tsSn6I4ulcWaax6VsOS5n/Jrhf9V+Nz0
|
||||||
|
e0CjrOJeLUSd2vTiyz03ho32nIrsGFeNDHhFmKzXNynjcW3z6Mx9/dSjqPHn8wFn
|
||||||
|
0dcVkq0GAZ+L8woNyDhr4e7Hhy6FEDhRX9bWz7FlswnBzym2AzU/zhagDrP2MIQ9
|
||||||
|
o/cjAp3AoX70IIF7kIha8OXG4ew4tHpQIAj9E5SG5uEeD6gVTg7HB93dH+NEeLNR
|
||||||
|
F65wzQHPhf+V2zR7qlpCg3gqzyE9mvvTN/DMAJwASXWXK37ITIjcXhErH/2shHSK
|
||||||
|
jRwYuQiAWuhPrAr04EUoeHQFM6EAKDqBsFRr+/Ot34DqhAwAOlmkyCM/mw7IDl73
|
||||||
|
pX/Ey9CQc2u2PS0zqYkDIzWE1Osek3FIF6JHSzaiRYODjaE6v15FLe7hZ56x6NMI
|
||||||
|
EDzpScp6HwMg6jsudbONrz3KwvXE6j4+yWpp8tgkZzwsIfnOMtWSrGgX4Oz9Y0Ep
|
||||||
|
jZ1yKDLQBjKZor2z4FBNze74tEnzJdBl59mQoN3ffFWHRlnkOhAZ+HsaXoIKB6AZ
|
||||||
|
3NPzlgGhOmtza6HKAwlseMgjpNH7EmeTLplr5nUlEMwwJ2+i8VGS4J9458vipcBm
|
||||||
|
50MbqaR8N8jAsanxyAbBF7gLPzsezN9Kzus99B6uB2nZIgZIsOeCeBLEZGzz9bUv
|
||||||
|
AiI3rM38nFkkKeHjJwqcYsO6LiLxdstnaHSveE7d6ugmN3+bNaZ+QCUDFlfELr4M
|
||||||
|
n5Eiwv3+hlCL8UBubacdQypfYvYI/z8rTdH+5eTVxBjeTCG0QmjDlYiuHcycL+q3
|
||||||
|
iE3WxQ0yGpeJL6V985Ej/0SHkudTI49T5o+jcp6HFNS7xQelBGndpXGq9Xyc+OV/
|
||||||
|
c+1zYWxz/sMFAbMkCF2pMMLfKL5EBkLBCpOdxHtWFt9JMxuhRfl3YzN81xSV1m0A
|
||||||
|
FtURjPUSwGqI7QFPl4R4MA1+0AdveZYprGo2nSUQpSoCW9WHl8U8tLIUNtew1MPA
|
||||||
|
v19h5DSqJ3tj+pPdxmFixoG+hBNk8FO1lJvH6P2an6lYVTlZ3/uDU5/DHTUTHuen
|
||||||
|
9x8FY3LQDQZA65QEnN092cUxXWw6cha+xXvwrwZGZiWXtuMBh/mfa4apxQ5+KY0N
|
||||||
|
/RCbIBctvLgijuC+xQw8GqBcG5L7urCOhnyZCXspWW7+loI7uZ0EfpNi7jY8BmAs
|
||||||
|
3S3MwMwwp+uvloikg3ZTUJmx/g==
|
||||||
|
-----END ENCRYPTED PRIVATE KEY-----
|
||||||
28
certs/CA/private/key.pem
Normal file
28
certs/CA/private/key.pem
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDR4SqyWdolxuGp
|
||||||
|
NqVUxBM1Pz+zr3nustLDvxxlJVAQhI0zDJLhDm1hgnNdcSarKnysExYACt9YZo87
|
||||||
|
yyAqzqsL1Rxi6tWvUZvG2xV1FFEj9+C23au1ZBh2QEjsPP2+ORxQXW1bDJ1bM+0q
|
||||||
|
6fMcar1c1Zjw28nh6rl2c7WgXXDI9Z7ltspREmkDeA5e4K0Rj5M6/77w8re909Ce
|
||||||
|
QTWkVGdN8E/muZq2dF4WVt/EJQLByepflrT4F4NqfDgZKbibQwIVcZYRvTs/7wlR
|
||||||
|
1Q/7atswhnBRvjYEHP9QoF6DjXS+DCE8hLJB61bhcafdssmT3fcYU7qC85T0JV3M
|
||||||
|
93FE88s9AgMBAAECggEBAJ4N3aOWeuv8DDDsUDG3v2dYDFb7DO4viP2UzIN3SAZI
|
||||||
|
bDVHgM0vmjvWi56Q+bfpNR44+VZq2auvAKrc24qhPLqacNglVWrEIHXuwwf6luhp
|
||||||
|
YbcU1JyzlqBm5QPTIpvAUoxK73Gj3FS0lCS/Li/2SkjBKn4oZq5Vn/mORfdQpwEO
|
||||||
|
aY5YzNNCzCwbqL/XcwvN1XRpF9puhBb+o2TtfGPLBf5LiScJWxrtPM7q+dFG/Ef7
|
||||||
|
f9L28iX+W6e3lD0/d93XEtlh1IsaZTWkl4m01Etg0f2FFtcja38eG+Yys4YNSpPe
|
||||||
|
VUWtFL2eDX+u5wv7nbaG9GsZmithXL6xa0PdwOd0KAECgYEA/D7K2KcxS+HeBgcG
|
||||||
|
V3b0m6QH1bRbqRxzK22TKnCVLAlRW7+5bfK56sHqMrOnDp/PuyAdq93roeJBNnAg
|
||||||
|
DrUt9i/lFTZ2kSBTJIlKW7cHv2Hocw3sQSDY/PhAXtALULxa8NHadPGsntHQDLzq
|
||||||
|
TVUNhOU6+GqPcqTp8BeSy+XrGD0CgYEA1QDvbxh/5tQxCFXcKn5KQCpJhbCfKBSW
|
||||||
|
drd8YF3Gdpxt3d2vTFTOsQ3xyAvEhSV0Jy9WUXobmx+KK8OjZqsR4PoYpYN3IWIW
|
||||||
|
N9jBKE9bahPRFF3g3FxaeVrCBkZBVK+ZAv/E5gshCyCXOmgHT+Kgi1Xpb4X0J/Pj
|
||||||
|
T83O7y/DrwECgYEAlagfq4kOy2m307WpQo12NT2N1HIMi/owLoz1apGfP6BLn/bR
|
||||||
|
anU/bxQcAnbqX9fbZLd6QzUIj2Rwsn611mQSX+Ussng8wn/+NEgsUNv4AclyI4kY
|
||||||
|
Kc/2TahwcJBYlk8aWZT4WrfkZdov6M7Q6I7U1Ys3gJmggDFuBp/F3amM4TUCgYBv
|
||||||
|
IofKvUuukjPrF3FCMpZnGCk4Ockwzl0bH+0jEoqERGYGhq35853MP2xRZeJpXftW
|
||||||
|
npo49K8GptQetXrCDKvNaHAHm6pCnrzc8pkFgTI/DIvgRoMWNpF2E+Z8A5f+qYf6
|
||||||
|
SpzfMhtRhPWiobh+8820JWUgrRGQ7u1BLC62GYPOAQKBgG/rt2Xu42DJcQ/wm7wN
|
||||||
|
LB8zn54G/rKWq+mXQbenpr1lN4zBOGgZILrRb1HTcQ7a19056BHBmx3J52yQAnGH
|
||||||
|
cxuhOyppsH3WgUkUqYN4bzO1CJorX3cP9udBAcmN4BWLF7pLgT7zHrLkJg05sWnd
|
||||||
|
UHWm2y7vdXeTvdD381p0Ug0S
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
1
certs/CA/serial
Normal file
1
certs/CA/serial
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
99BD46167599BDB6
|
||||||
1
certs/CA/serial.old
Normal file
1
certs/CA/serial.old
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
99BD46167599BDB5
|
||||||
114
certs/cert.pem
114
certs/cert.pem
|
|
@ -1,33 +1,85 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 11078087720677522869 (0x99bd46167599bdb5)
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
Issuer: C=US, ST=California, O=Bumper, OU=Bumper, CN=Bumper
|
||||||
|
Validity
|
||||||
|
Not Before: Feb 1 06:17:18 2019 GMT
|
||||||
|
Not After : Jan 29 06:17:18 2029 GMT
|
||||||
|
Subject: CN=Bumper
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (2048 bit)
|
||||||
|
Modulus:
|
||||||
|
00:d1:e1:2a:b2:59:da:25:c6:e1:a9:36:a5:54:c4:
|
||||||
|
13:35:3f:3f:b3:af:79:ee:b2:d2:c3:bf:1c:65:25:
|
||||||
|
50:10:84:8d:33:0c:92:e1:0e:6d:61:82:73:5d:71:
|
||||||
|
26:ab:2a:7c:ac:13:16:00:0a:df:58:66:8f:3b:cb:
|
||||||
|
20:2a:ce:ab:0b:d5:1c:62:ea:d5:af:51:9b:c6:db:
|
||||||
|
15:75:14:51:23:f7:e0:b6:dd:ab:b5:64:18:76:40:
|
||||||
|
48:ec:3c:fd:be:39:1c:50:5d:6d:5b:0c:9d:5b:33:
|
||||||
|
ed:2a:e9:f3:1c:6a:bd:5c:d5:98:f0:db:c9:e1:ea:
|
||||||
|
b9:76:73:b5:a0:5d:70:c8:f5:9e:e5:b6:ca:51:12:
|
||||||
|
69:03:78:0e:5e:e0:ad:11:8f:93:3a:ff:be:f0:f2:
|
||||||
|
b7:bd:d3:d0:9e:41:35:a4:54:67:4d:f0:4f:e6:b9:
|
||||||
|
9a:b6:74:5e:16:56:df:c4:25:02:c1:c9:ea:5f:96:
|
||||||
|
b4:f8:17:83:6a:7c:38:19:29:b8:9b:43:02:15:71:
|
||||||
|
96:11:bd:3b:3f:ef:09:51:d5:0f:fb:6a:db:30:86:
|
||||||
|
70:51:be:36:04:1c:ff:50:a0:5e:83:8d:74:be:0c:
|
||||||
|
21:3c:84:b2:41:eb:56:e1:71:a7:dd:b2:c9:93:dd:
|
||||||
|
f7:18:53:ba:82:f3:94:f4:25:5d:cc:f7:71:44:f3:
|
||||||
|
cb:3d
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:FALSE
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
26:7E:4F:14:80:43:9E:EF:64:C2:61:6E:81:68:4B:6A:F2:4E:B5:40
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:5E:39:C7:78:31:83:30:51:82:D9:88:9B:73:0B:72:EF:44:74:7B:D5
|
||||||
|
|
||||||
|
X509v3 Subject Alternative Name:
|
||||||
|
DNS:*.ecouser.net, DNS:*.ecovacs.com
|
||||||
|
X509v3 Key Usage: critical
|
||||||
|
Digital Signature, Key Encipherment
|
||||||
|
X509v3 Extended Key Usage: critical
|
||||||
|
TLS Web Server Authentication, TLS Web Client Authentication
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
89:11:41:18:ac:22:0d:4a:08:31:ee:18:d4:cf:88:02:93:f0:
|
||||||
|
ef:53:f5:86:e1:b2:16:0e:8d:87:f9:e9:29:7a:aa:95:4e:d7:
|
||||||
|
89:96:ac:1c:9a:19:8c:84:62:41:7a:b8:8c:46:07:8f:56:ae:
|
||||||
|
18:31:52:cb:60:3f:01:57:4d:65:d6:5f:95:da:bc:c1:53:35:
|
||||||
|
fa:cc:c6:49:aa:fe:35:d3:05:15:2c:bd:9b:ac:f0:36:d7:71:
|
||||||
|
bc:ed:3e:f9:ac:c8:84:58:5e:f0:02:02:47:9c:0f:f3:f6:06:
|
||||||
|
35:4d:3b:69:2f:bb:81:cd:ae:61:84:75:97:b1:c2:e7:e1:74:
|
||||||
|
18:61:bc:c0:6b:2e:4d:f6:61:0d:36:72:3d:e2:ef:ef:e5:cf:
|
||||||
|
7a:17:af:d6:7c:d2:a4:88:ca:b3:4e:27:7e:f3:da:96:33:ef:
|
||||||
|
12:12:02:5a:31:70:82:e9:b6:e4:a9:57:d7:a4:b3:f6:3f:ea:
|
||||||
|
fa:63:6a:e6:b7:26:73:bb:c1:87:4e:e2:13:40:65:b9:04:3c:
|
||||||
|
c9:95:d7:8e:07:6f:9d:af:d2:36:ee:aa:5d:e5:f9:bb:cc:47:
|
||||||
|
9c:ce:62:cf:28:2e:a3:58:8b:63:a2:e3:c3:f4:b0:69:98:aa:
|
||||||
|
84:ff:a5:c2:52:8b:e8:b4:5c:2c:67:ef:63:b3:5c:42:95:6d:
|
||||||
|
6b:d7:5b:11
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIFtTCCA52gAwIBAgIJAKLG5udzkR+zMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
MIIDkzCCAnugAwIBAgIJAJm9RhZ1mb21MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV
|
||||||
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQ8wDQYDVQQKEwZCdW1wZXIxDzAN
|
||||||
aWRnaXRzIFB0eSBMdGQwHhcNMTcxMjA1MTkxNTQwWhcNMTgxMjA1MTkxNTQwWjBF
|
BgNVBAsTBkJ1bXBlcjEPMA0GA1UEAxMGQnVtcGVyMB4XDTE5MDIwMTA2MTcxOFoX
|
||||||
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
|
DTI5MDEyOTA2MTcxOFowETEPMA0GA1UEAwwGQnVtcGVyMIIBIjANBgkqhkiG9w0B
|
||||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
AQEFAAOCAQ8AMIIBCgKCAQEA0eEqslnaJcbhqTalVMQTNT8/s6957rLSw78cZSVQ
|
||||||
CgKCAgEAu1z0Iw7KLJZD8CBrZWm9Jp7eqIAqdoTn3GWmNmzgIDnzmxZb70War74d
|
EISNMwyS4Q5tYYJzXXEmqyp8rBMWAArfWGaPO8sgKs6rC9UcYurVr1GbxtsVdRRR
|
||||||
7aWDVtEVtOZQ8ShW15zRanr6hQkHdM5Li1KTMYW3P7TOmzJZkESNEZviOyvlfRPG
|
I/fgtt2rtWQYdkBI7Dz9vjkcUF1tWwydWzPtKunzHGq9XNWY8NvJ4eq5dnO1oF1w
|
||||||
oLLbPIp+A7W8Zz3ST0cDzyy6N7LRAA0OywRgPhrxrN637Fx1+SFwqKQg0IUOuX3v
|
yPWe5bbKURJpA3gOXuCtEY+TOv++8PK3vdPQnkE1pFRnTfBP5rmatnReFlbfxCUC
|
||||||
veZ1oAZSBUDzpDPUaak7jXugB11wt6XTEekwOcrH7WEDoFH87thtJyCM7IidW0bM
|
wcnqX5a0+BeDanw4GSm4m0MCFXGWEb07P+8JUdUP+2rbMIZwUb42BBz/UKBeg410
|
||||||
rkqxeiM18qMzn8cGqnm2nY+VHgK0t0/QZ/cftPVc2IHZLr/+eTGaJmPv7gL+7v/l
|
vgwhPISyQetW4XGn3bLJk933GFO6gvOU9CVdzPdxRPPLPQIDAQABo4GpMIGmMAkG
|
||||||
7fgk3RMTn0GrdTO+Vvnu1qNK7r+//KMgGOo6zwEQAI9Ml6y1LRuEXrymgsYY9qCE
|
A1UdEwQCMAAwHQYDVR0OBBYEFCZ+TxSAQ57vZMJhboFoS2ryTrVAMB8GA1UdIwQY
|
||||||
ZtBV0FoZaVYAuGG4cvCDLLPgM2OivIcWgG+77MqMH/RkWg+BrUesn7ISEpF+7Q7+
|
MBaAFF45x3gxgzBRgtmIm3MLcu9EdHvVMCcGA1UdEQQgMB6CDSouZWNvdXNlci5u
|
||||||
jc6pFYr4y5fc2jJ9/c5Mxu6sv0Tc8N/c9WpcXsJ6Y1JrBbDcdGSwmTZ1JVyiog9O
|
ZXSCDSouZWNvdmFjcy5jb20wDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQG
|
||||||
6bU/16QTuU3w2oH9qjnc907tZuB66P53WJ2iHPvB8yF5V93FvtbPYsHpO7noURSI
|
CCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAiRFBGKwiDUoI
|
||||||
LqvKoNy31ndv4kao2AIzrJuhBBkSorBEcoFerRRBJMHa85R5Tb6iuGg6RFv0BB8K
|
Me4Y1M+IApPw71P1huGyFg6Nh/npKXqqlU7XiZasHJoZjIRiQXq4jEYHj1auGDFS
|
||||||
gv6mG/7oqmR8QDnRRd54U2pEsIkYmgoIU4/8L91r9Vd1eiEDEnsCAwEAAaOBpzCB
|
y2A/AVdNZdZfldq8wVM1+szGSar+NdMFFSy9m6zwNtdxvO0++azIhFhe8AICR5wP
|
||||||
pDAdBgNVHQ4EFgQUqA/GBsZAepFIcbgw6gK/HTUWolswdQYDVR0jBG4wbIAUqA/G
|
8/YGNU07aS+7gc2uYYR1l7HC5+F0GGG8wGsuTfZhDTZyPeLv7+XPehev1nzSpIjK
|
||||||
BsZAepFIcbgw6gK/HTUWoluhSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT
|
s04nfvPaljPvEhICWjFwgum25KlX16Sz9j/q+mNq5rcmc7vBh07iE0BluQQ8yZXX
|
||||||
b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQCi
|
jgdvna/SNu6qXeX5u8xHnM5izyguo1iLY6Ljw/SwaZiqhP+lwlKL6LRcLGfvY7Nc
|
||||||
xubnc5EfszAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQB7kHEHKNDQ
|
QpVta9dbEQ==
|
||||||
TcvwY9z3RscpHyWwjrGjUdjk8AkM9cTm/sT8G7JU/7+KZueu4elNOc4hir/+DMlO
|
|
||||||
/AN6IXRNrfPO8IVG6hKnK9K3wvceJzg6mT8F4peg4vMNdIsrZyQaPhWArUF7LG/Q
|
|
||||||
HF4Yk8jnw3NKIG5fnlagAGFDdDGSdDK/IQN+JA6sHJVQRrBLpksRUCWFY7QJGYVS
|
|
||||||
DmEAYr6Y6emftbflUjW6iC/E57lC5F7bQyoIEIBBHKjMaNTdM8TzOUZSOGLwAuVD
|
|
||||||
C1iAoZn7VWeGa4h/xwx0yio0yeI6gRh3fINBKoCNv6pLUm6sgrW8oqGMDJiH0T3H
|
|
||||||
sc8xC3bK2DPO4sIyisL3HUPcsSFB/hf0i8BOQrihrpZEn/fx4lTRyQYSKT5NVSl4
|
|
||||||
v/9hmDjWIXh9EInGfP8DdWp4Og8GiXcV9rUqvRBo5aBwqK/aTNRCk6ah5N958yFu
|
|
||||||
wHlrbQmPnw1AqJSG1oG+4dcXnrcgd/xGMAarTbgzqsd8icafg8v2x2pG5ZexE3uN
|
|
||||||
0METK9hvCrjN8/Rvo2ViTDa8WF4CXMuVFG4FdbeRrYPZl1j1jBYDMtKkVJJCsVQd
|
|
||||||
chYbqKgNDmMcbfpksBtF3XpYTtVNAKA9g5mrAhEJS88uy63PStLoBQNXv9y8ksql
|
|
||||||
VVCTFNGy5JRyvOeSRYSSdXjKbfj5Vgh1HQ==
|
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,28 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
MIIJJwIBAAKCAgEAu1z0Iw7KLJZD8CBrZWm9Jp7eqIAqdoTn3GWmNmzgIDnzmxZb
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDR4SqyWdolxuGp
|
||||||
70War74d7aWDVtEVtOZQ8ShW15zRanr6hQkHdM5Li1KTMYW3P7TOmzJZkESNEZvi
|
NqVUxBM1Pz+zr3nustLDvxxlJVAQhI0zDJLhDm1hgnNdcSarKnysExYACt9YZo87
|
||||||
OyvlfRPGoLLbPIp+A7W8Zz3ST0cDzyy6N7LRAA0OywRgPhrxrN637Fx1+SFwqKQg
|
yyAqzqsL1Rxi6tWvUZvG2xV1FFEj9+C23au1ZBh2QEjsPP2+ORxQXW1bDJ1bM+0q
|
||||||
0IUOuX3vveZ1oAZSBUDzpDPUaak7jXugB11wt6XTEekwOcrH7WEDoFH87thtJyCM
|
6fMcar1c1Zjw28nh6rl2c7WgXXDI9Z7ltspREmkDeA5e4K0Rj5M6/77w8re909Ce
|
||||||
7IidW0bMrkqxeiM18qMzn8cGqnm2nY+VHgK0t0/QZ/cftPVc2IHZLr/+eTGaJmPv
|
QTWkVGdN8E/muZq2dF4WVt/EJQLByepflrT4F4NqfDgZKbibQwIVcZYRvTs/7wlR
|
||||||
7gL+7v/l7fgk3RMTn0GrdTO+Vvnu1qNK7r+//KMgGOo6zwEQAI9Ml6y1LRuEXrym
|
1Q/7atswhnBRvjYEHP9QoF6DjXS+DCE8hLJB61bhcafdssmT3fcYU7qC85T0JV3M
|
||||||
gsYY9qCEZtBV0FoZaVYAuGG4cvCDLLPgM2OivIcWgG+77MqMH/RkWg+BrUesn7IS
|
93FE88s9AgMBAAECggEBAJ4N3aOWeuv8DDDsUDG3v2dYDFb7DO4viP2UzIN3SAZI
|
||||||
EpF+7Q7+jc6pFYr4y5fc2jJ9/c5Mxu6sv0Tc8N/c9WpcXsJ6Y1JrBbDcdGSwmTZ1
|
bDVHgM0vmjvWi56Q+bfpNR44+VZq2auvAKrc24qhPLqacNglVWrEIHXuwwf6luhp
|
||||||
JVyiog9O6bU/16QTuU3w2oH9qjnc907tZuB66P53WJ2iHPvB8yF5V93FvtbPYsHp
|
YbcU1JyzlqBm5QPTIpvAUoxK73Gj3FS0lCS/Li/2SkjBKn4oZq5Vn/mORfdQpwEO
|
||||||
O7noURSILqvKoNy31ndv4kao2AIzrJuhBBkSorBEcoFerRRBJMHa85R5Tb6iuGg6
|
aY5YzNNCzCwbqL/XcwvN1XRpF9puhBb+o2TtfGPLBf5LiScJWxrtPM7q+dFG/Ef7
|
||||||
RFv0BB8Kgv6mG/7oqmR8QDnRRd54U2pEsIkYmgoIU4/8L91r9Vd1eiEDEnsCAwEA
|
f9L28iX+W6e3lD0/d93XEtlh1IsaZTWkl4m01Etg0f2FFtcja38eG+Yys4YNSpPe
|
||||||
AQKCAgBmUpJl3vrctMeviRT90mxhfhvda/lEtrqla8IFMRqJnvyCMwjMOHgNCEfx
|
VUWtFL2eDX+u5wv7nbaG9GsZmithXL6xa0PdwOd0KAECgYEA/D7K2KcxS+HeBgcG
|
||||||
9BUxQYiYPbvwp/YdVGAtxbGxG8g9qzgog1Utk3gLS6QVr76oAjgEaNx5MjTnRPiR
|
V3b0m6QH1bRbqRxzK22TKnCVLAlRW7+5bfK56sHqMrOnDp/PuyAdq93roeJBNnAg
|
||||||
5HvEqWG+hu64d1O2xfK3nxanunlXjMoKZ35kDHT10sAhTjGDeBa9krz1A+WRucxH
|
DrUt9i/lFTZ2kSBTJIlKW7cHv2Hocw3sQSDY/PhAXtALULxa8NHadPGsntHQDLzq
|
||||||
KmgMl8cNZXZps4CKn8iNMPmpbAdV1DLcMjyLI0piQjwnvv7iNcIOv0EXIFjHc3fu
|
TVUNhOU6+GqPcqTp8BeSy+XrGD0CgYEA1QDvbxh/5tQxCFXcKn5KQCpJhbCfKBSW
|
||||||
q44XRMl+lFBqLtheG5B2peZq+FFXVzhavuRLAEDPYMKKPIIuD7++vY9Xrb0qKwiY
|
drd8YF3Gdpxt3d2vTFTOsQ3xyAvEhSV0Jy9WUXobmx+KK8OjZqsR4PoYpYN3IWIW
|
||||||
GI+RSIzLyaJopHSLrX0ZLR6MGp4u3D47EwdN6Epir94lLW7lqeyLzXyQN9YWCCiD
|
N9jBKE9bahPRFF3g3FxaeVrCBkZBVK+ZAv/E5gshCyCXOmgHT+Kgi1Xpb4X0J/Pj
|
||||||
f6+aa3CfO6892ToPFstliD1+3o2ghaTG2ypbfK+OaVWwaUzI2TXgFM08b3SPpQaS
|
T83O7y/DrwECgYEAlagfq4kOy2m307WpQo12NT2N1HIMi/owLoz1apGfP6BLn/bR
|
||||||
xgsanJhVHBSVZgyTU++hSZJcmIFPm0creEdB2HFm/ef0nABoYqRMADOToZSOaIUU
|
anU/bxQcAnbqX9fbZLd6QzUIj2Rwsn611mQSX+Ussng8wn/+NEgsUNv4AclyI4kY
|
||||||
eKy0bLGTkBo5F1lV9s8Bz9GsDihORiwMucJ2Q0k8i6kE26oudMm4dOD8m+Q9Q+i1
|
Kc/2TahwcJBYlk8aWZT4WrfkZdov6M7Q6I7U1Ys3gJmggDFuBp/F3amM4TUCgYBv
|
||||||
cuC2oujD6qGbQx0uagAiNE8OHsvTZ1N7LpB7Z8ipz6k5D5xELsieFzGkj+ZBUh7B
|
IofKvUuukjPrF3FCMpZnGCk4Ockwzl0bH+0jEoqERGYGhq35853MP2xRZeJpXftW
|
||||||
ilRpBO0DLie7e90HICpAuOckpcgU+2Ek/WgFzNl8eEtAl44VOQKCAQEA5HNF8Pni
|
npo49K8GptQetXrCDKvNaHAHm6pCnrzc8pkFgTI/DIvgRoMWNpF2E+Z8A5f+qYf6
|
||||||
rjadcOcYXzdVRQp44sjErbUjTiqzsoZrcDYExZQPKPjOIzACjiUzJT6uiuTJcaye
|
SpzfMhtRhPWiobh+8820JWUgrRGQ7u1BLC62GYPOAQKBgG/rt2Xu42DJcQ/wm7wN
|
||||||
2Y4j4S11M9kKY4E41bmXsIBfioNLd5GfSCYm3qdMTy5hOSOUCLxSA2rQ+QGYMGGc
|
LB8zn54G/rKWq+mXQbenpr1lN4zBOGgZILrRb1HTcQ7a19056BHBmx3J52yQAnGH
|
||||||
1EJwGnx5dxS8G1K9tyIC1aSiYJivvGstNEWy+2SIWWfHIg9s6edEKhLo+lMVb0aZ
|
cxuhOyppsH3WgUkUqYN4bzO1CJorX3cP9udBAcmN4BWLF7pLgT7zHrLkJg05sWnd
|
||||||
JLrtE0XWKS9u5meZdKpib20WfNI2XCKH7bs8wKagHEvOv1xky1R0+5kJ61bUhuto
|
UHWm2y7vdXeTvdD381p0Ug0S
|
||||||
3kdUtd6RmcXRlAhxRUGta9PszKVBaUgMsKIaWG9ArgrNs3pwg6mAy+eo33DdREN6
|
-----END PRIVATE KEY-----
|
||||||
AcFODJG+TVEGZwKCAQEA0fU8MbiQ1HtAYju36WPQYP4b8GT1RmaSsiRJRwexHD/q
|
|
||||||
HbFZ43CVWni03G4YU4fNjzUUg3gfJgV+ho6fdMJvTneCy0jJEQnjlo/RN4N9nbgL
|
|
||||||
fyrxhtsvfKus9NyxA3YzaPNc8b87f7c4kXgKCKJfiUyWGye0jBkK/1U1Se0R4eko
|
|
||||||
816ZktIPWjrJGItlsrv/XnhQVDjhvAIjvVnVfIvEtpbie1zOxTFtRm+DkZwkRBmn
|
|
||||||
2+0SjWjy3Wmi5GVne8LslS9nRk5uOw2//nXB/1rqWGNaiwW5PPeprv416OVxQGY4
|
|
||||||
MKbEp3H2CeTPW9MWaX4zCfI0d1TANXdGGNDjJ0w+zQKCAQAWRyN/725qnMrXssGO
|
|
||||||
r3+yPuYw/3Emm2549fMWKsOZarsmZVzhGvpHNixZnkkRjb5Jryvx2OqYvqLDF8LB
|
|
||||||
Lp4zu+tF9FZVkP9AK7OVUm8dAxNxTRFE+3nhr5WFEJBS4vzD+6LSvQIlNOFnw9G4
|
|
||||||
ih4Z8ASuMjPij66CCwHANxdubImauGAPj+48ZRKy8KmWDMgJeUay1kii61OpOHs9
|
|
||||||
AvImp92wIdl9kj7KJ1nDvF5TEZpIEzez6rSbtq/hoDfUPUVZXNtW2OgeHQimv7Cl
|
|
||||||
NURN0lDxdwkpZb5G6qqpUKxkITq+ewLstgL/SKZmQIZd3BZ8y63YNVhViXtKAIB1
|
|
||||||
W7xrAoIBACZi3cSBaAlRF25eWLuVAi4Zh2YmLynV6xAizjrsTxdJIDaZjEOm+8d0
|
|
||||||
tixu/IeZFp4OKFf5CEjdTEqxnLmuKsd/1ivwfMJeunt2g9vQcJD7ay6u3UW4oS/7
|
|
||||||
+z0xdDOb0r5ncCDKw8gP6Ym/FqaJeUoS9Ia2da3/jiEspPeFHnXdOR11uEI9oYzv
|
|
||||||
noCcS9WnxYKyII2lcK/8/CfMWtnQfACeCX5gc3VeGgp8rONcVs0Um7n72B5+h5iv
|
|
||||||
jZaQa6EW/EYySoFyKXkmhGmzgep75siOMamlPD7HUQCrfZO9Dz2k8rUCaIXssf0R
|
|
||||||
FlBFgZ178zzoSWtf1VxxGCHFXyJ6MCkCggEAPbaIna3rDPMQbw34Zhd1LrUvtiRG
|
|
||||||
G1kARdR/LTA+j8wXSI4ONlhHT50g3sxyIW1UcPTr4tlJRECQ97XaY5HPP+FovWVV
|
|
||||||
joOnFgVn9hklo8EdUbc2jFFtPZOwqikfocxf8H4XfR58Ak7/lKzwegwM7MciF5r7
|
|
||||||
19WVi/QQZa2gRTFrn78eElUnAu33AlW2SHELwg4Lh2zbJK2wYIfT3c7GB4qhFLch
|
|
||||||
+MF7GOzLmkBYb23IE+S7ddciH0OnAOLMHxxFUx+DdQ2hPobj/Y0oqUdEl5wqLvZ+
|
|
||||||
4TUdzCay4b1hBoXKD1dVv1VUtt1XGB8AZYOdVkbInDDWJuuJLgY93Qaeqg==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
|
|
|
||||||
BIN
docs/images/emailcert.png
Normal file
BIN
docs/images/emailcert.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/images/ios_cert_trust.png
Normal file
BIN
docs/images/ios_cert_trust.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/images/ios_cert_trust_continue.png
Normal file
BIN
docs/images/ios_cert_trust_continue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/images/ios_cert_warning_install.png
Normal file
BIN
docs/images/ios_cert_warning_install.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/images/ios_email_cert.png
Normal file
BIN
docs/images/ios_email_cert.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
docs/images/ios_install_profile.png
Normal file
BIN
docs/images/ios_install_profile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -2,19 +2,22 @@
|
||||||
|
|
||||||
from sucks import *
|
from sucks import *
|
||||||
|
|
||||||
|
|
||||||
class BumperVacBot(VacBot):
|
class BumperVacBot(VacBot):
|
||||||
def __init__(self, server_address):
|
def __init__(self, server_address):
|
||||||
self.server_address = server_address
|
self.server_address = server_address
|
||||||
vacuum = { 'did':'none','class':'none' }
|
vacuum = {"did": "none", "class": "none"}
|
||||||
super().__init__('sucks', 'ecouser.net', '', '', vacuum, '')
|
super().__init__("sucks", "ecouser.net", "", "", vacuum, "")
|
||||||
|
|
||||||
def connect_and_wait_until_ready(self):
|
def connect_and_wait_until_ready(self):
|
||||||
logging.info('connecting')
|
logging.info("connecting")
|
||||||
self.xmpp.connect(self.server_address)
|
self.xmpp.connect(self.server_address)
|
||||||
self.xmpp.process()
|
self.xmpp.process()
|
||||||
self.xmpp.wait_until_ready()
|
self.xmpp.wait_until_ready()
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s')
|
|
||||||
server_address = ('xxx.xxx.xxx.xxx', 5223)
|
logging.basicConfig(level=logging.DEBUG, format="%(levelname)-8s %(message)s")
|
||||||
|
server_address = ("xxx.xxx.xxx.xxx", 5223)
|
||||||
|
|
||||||
# Initialize
|
# Initialize
|
||||||
vacbot = BumperVacBot(server_address)
|
vacbot = BumperVacBot(server_address)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue