├── config.py
├── dependencies.txt
├── ssshare
├── models.py
├── ss
│ ├── __init__.py
│ ├── lib
│ │ └── geckodriver
│ ├── utils.py
│ ├── ssr_check.py
│ ├── ss_local.py
│ ├── parse.py
│ └── crawler.py
├── static
│ ├── bootstrap.css
│ ├── index.css
│ ├── favicon.ico
│ └── wechat.png
├── shadowsocks
│ ├── tail.sh
│ ├── crypto
│ │ ├── lib
│ │ │ ├── libsodium.a
│ │ │ ├── libsodium.so
│ │ │ ├── libsodium.so.23
│ │ │ ├── libsodium.so.23.0.0
│ │ │ ├── pkgconfig
│ │ │ │ └── libsodium.pc
│ │ │ └── libsodium.la
│ │ ├── __init__.py
│ │ ├── rc4_md5.py
│ │ ├── util.py
│ │ ├── ctypes_libsodium.py
│ │ ├── sodium.py
│ │ ├── ctypes_openssl.py
│ │ ├── openssl.py
│ │ └── table.py
│ ├── stop.sh
│ ├── run.sh
│ ├── logrun.sh
│ ├── version.py
│ ├── __init__.py
│ ├── obfsplugin
│ │ ├── __init__.py
│ │ ├── plain.py
│ │ └── verify.py
│ ├── encrypt_test.py
│ ├── local.py
│ ├── obfs.py
│ ├── lru_cache.py
│ ├── daemon.py
│ ├── encrypt.py
│ ├── eventloop.py
│ ├── ordereddict.py
│ ├── server.py
│ └── manager.py
├── main.py
├── __init__.py
├── templates
│ ├── 404.html
│ ├── donate.html
│ ├── full.html
│ ├── clients.html
│ ├── base.html
│ ├── pages.html
│ ├── index.html
│ └── notes.html
├── config.py
├── js
│ ├── canvas-nest.min.js
│ ├── jquery.qrcode.js
│ └── clipboard.min.js
├── views.py
└── donation.py
├── Procfile
├── package.json
├── test
├── __init__.py
├── test_sodium.py
└── test_ssr_check.py
├── app.yaml
├── upload-pypi.sh
├── .travis-test.py
├── app.json
├── requirements.txt
├── .travis.yml
├── .github
└── FUNDING.yml
├── manage.py
├── app.py
├── .gcloudignore
├── setup.py
├── .gitignore
├── CONTRIBUTING.md
└── README.md
/config.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dependencies.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ssshare/models.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ssshare/ss/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ssshare/static/bootstrap.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ssshare/static/index.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn -b :$PORT ssshare.main:app
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ShadowSocksShare"
3 | }
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('..')
3 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/tail.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | tail -f ssserver.log
4 |
--------------------------------------------------------------------------------
/app.yaml:
--------------------------------------------------------------------------------
1 | runtime: python37
2 | entrypoint: gunicorn -b :$PORT ssshare.main:app
3 |
--------------------------------------------------------------------------------
/ssshare/ss/lib/geckodriver:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the0demiurge/ShadowSocksShare/HEAD/ssshare/ss/lib/geckodriver
--------------------------------------------------------------------------------
/ssshare/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the0demiurge/ShadowSocksShare/HEAD/ssshare/static/favicon.ico
--------------------------------------------------------------------------------
/ssshare/static/wechat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the0demiurge/ShadowSocksShare/HEAD/ssshare/static/wechat.png
--------------------------------------------------------------------------------
/ssshare/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from ssshare import app, views, models
4 | views.start()
--------------------------------------------------------------------------------
/upload-pypi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | python setup.py sdist bdist_wheel
3 | twine upload dist/*
4 |
5 | rm -r build/ dist/ ssshare.egg-info/
6 |
--------------------------------------------------------------------------------
/.travis-test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from test import test_sodium
3 | from ssshare.ss import crawler
4 | crawler.main()
5 | exit(0)
6 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/lib/libsodium.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the0demiurge/ShadowSocksShare/HEAD/ssshare/shadowsocks/crypto/lib/libsodium.a
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/lib/libsodium.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the0demiurge/ShadowSocksShare/HEAD/ssshare/shadowsocks/crypto/lib/libsodium.so
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/lib/libsodium.so.23:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the0demiurge/ShadowSocksShare/HEAD/ssshare/shadowsocks/crypto/lib/libsodium.so.23
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/lib/libsodium.so.23.0.0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the0demiurge/ShadowSocksShare/HEAD/ssshare/shadowsocks/crypto/lib/libsodium.so.23.0.0
--------------------------------------------------------------------------------
/ssshare/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from flask import Flask
5 |
6 | app = Flask(__name__)
7 | app.config.from_object("config")
8 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ShadowSocksShare",
3 | "buildpacks":[
4 | {"url": "heroku/nodejs"},
5 | {"url": "heroku/python"}
6 | ],
7 | "addons": [
8 | "papertrail"
9 | ]
10 | }
--------------------------------------------------------------------------------
/ssshare/shadowsocks/stop.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | python_ver=$(ls /usr/bin|grep -e "^python[23]\.[1-9]\+$"|tail -1)
4 | eval $(ps -ef | grep "[0-9] ${python_ver} server\\.py a" | awk '{print "kill "$2}')
5 |
6 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | apscheduler
2 | beautifulsoup4
3 | cfscrape
4 | Flask
5 | Flask-Script
6 | gunicorn
7 | js2py
8 | numpy
9 | pillow
10 | pycryptodome
11 | pysocks
12 | regex
13 | requests
14 | zbar-py
15 |
--------------------------------------------------------------------------------
/ssshare/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}页面不存在{% endblock %}
4 |
5 | {% block content %}
6 |
404页面不存在
7 | 所以还是点我返回主页吧
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | python:
4 | - 3.5
5 | - 3.6
6 | - nightly
7 |
8 | env:
9 | global:
10 | - PORT=5001
11 | - TEST=1
12 |
13 | install:
14 | - "pip install -r requirements.txt"
15 |
16 | script:
17 | - "python ./.travis-test.py"
18 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd `dirname $0`
3 | python_ver=$(ls /usr/bin|grep -e "^python[23]\.[1-9]\+$"|tail -1)
4 | eval $(ps -ef | grep "[0-9] ${python_ver} server\\.py a" | awk '{print "kill "$2}')
5 | ulimit -n 512000
6 | nohup ${python_ver} server.py a>> /dev/null 2>&1 &
7 |
8 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/logrun.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd `dirname $0`
3 | python_ver=$(ls /usr/bin|grep -e "^python[23]\.[1-9]\+$"|tail -1)
4 | eval $(ps -ef | grep "[0-9] ${python_ver} server\\.py a" | awk '{print "kill "$2}')
5 | ulimit -n 512000
6 | nohup ${python_ver} server.py a>> ssserver.log 2>&1 &
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [the0demiurge]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | custom: # Replace with a single custom sponsorship URL
9 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/lib/pkgconfig/libsodium.pc:
--------------------------------------------------------------------------------
1 | prefix=/tmp/build_da28d797fa3200ed2b83d92a7b6c8796/the0demiurge-ShadowSocksShare-a92f662/vendor/libsodium
2 | exec_prefix=${prefix}
3 | libdir=${exec_prefix}/lib
4 | includedir=${prefix}/include
5 |
6 | Name: libsodium
7 | Version: 1.0.15
8 | Description: A modern and easy-to-use crypto library
9 |
10 | Libs: -L${libdir} -lsodium
11 | Cflags: -I${includedir}
12 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | from flask_script import Manager, Server
6 | from ssshare.main import app
7 |
8 | port = os.environ.get('PORT')
9 |
10 | manager = Manager(app)
11 |
12 | manager.add_command(
13 | "runserver", Server(host="0.0.0.0", port=port, use_debugger=False)
14 | )
15 | manager.add_command(
16 | "debug", Server(host="0.0.0.0", port=8080, use_debugger=True)
17 | )
18 |
19 | if __name__ == '__main__':
20 | manager.run()
21 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from ssshare.main import app as application
4 | import logging
5 | import os
6 | print(__name__)
7 | if 'PORT' in os.environ:
8 | port = int(os.environ['PORT'])
9 | else:
10 | port = 8080
11 |
12 |
13 | if __name__ == '__main__':
14 | try:
15 | # application.run(host='0.0.0.0', port=port)
16 | from wsgiref.simple_server import make_server
17 | httpd = make_server('0.0.0.0', port, application)
18 | httpd.serve_forever()
19 | except Exception as e:
20 | logging.exception(e, stack_info=True)
21 |
--------------------------------------------------------------------------------
/.gcloudignore:
--------------------------------------------------------------------------------
1 | # This file specifies files that are *not* uploaded to Google Cloud Platform
2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of
3 | # "#!include" directives (which insert the entries of the given .gitignore-style
4 | # file at that point).
5 | #
6 | # For more information, run:
7 | # $ gcloud topic gcloudignore
8 | #
9 | .gcloudignore
10 | # If you would like to upload your .git directory, .gitignore file or files
11 | # from your .gitignore file, remove the corresponding line
12 | # below:
13 | .git
14 | .gitignore
15 |
16 | # Python pycache:
17 | __pycache__/
18 | # Ignored by the build system
19 | /setup.cfg
--------------------------------------------------------------------------------
/ssshare/ss/utils.py:
--------------------------------------------------------------------------------
1 | import requests
2 | from urllib.parse import urlparse
3 | from urllib.robotparser import RobotFileParser
4 | import time
5 |
6 |
7 | def robots_get(url, *args, **kwargs):
8 | u = urlparse(url)
9 | robot_url = '{scm}://{loc}/robots.txt'.format(scm=u.scheme, loc=u.netloc)
10 | robot = RobotFileParser(robot_url)
11 | robot.read()
12 | ua = kwargs.get('headers', dict()).get('User-Agent', '*')
13 | if not robot.can_fetch(ua, url):
14 | return 'Not Allowed By robots.txt'
15 | delay = robot.crawl_delay(ua)
16 | if delay:
17 | time.sleep(delay)
18 | return requests.get(url, *args, **kwargs)
19 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017 breakwa11
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | # not use this file except in compliance with the License. You may obtain
8 | # a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 | # License for the specific language governing permissions and limitations
16 | # under the License.
17 |
18 | def version():
19 | return '3.4.0 2017-07-27'
20 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | #
3 | # Copyright 2012-2015 clowwindy
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 clowwindy
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/obfsplugin/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 clowwindy
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
--------------------------------------------------------------------------------
/ssshare/templates/donate.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %}免费ShadowSocks账号分享{% endblock %}
3 |
4 | {% block content %}
5 |
6 |
捐助信息
7 | 返回主页
8 |
17 |
18 | {{data|safe}}
19 |
20 |
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/test/test_sodium.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from ssshare import *
3 | import threading
4 | from ssshare.ss import crawler, ssr_check
5 | import requests
6 |
7 |
8 | def test2():
9 | data = '''
10 | {
11 | "server": "203.104.205.115",
12 | "server_ipv6": "::",
13 | "server_port": 8080,
14 | "local_address": "127.0.0.1",
15 | "local_port": 1080,
16 | "password": "yui",
17 | "group": "Charles Xu",
18 | "obfs": "tls1.2_ticket_auth",
19 | "method": "chacha20",
20 | "ssr_protocol": "auth_sha1_v4",
21 | "obfsparam": "",
22 | "protoparam": ""
23 | }'''
24 | w = ssr_check.test_socks_server(str_json=data)
25 | print('>>>>>>>结果:', w)
26 | if w is True:
27 | print(data)
28 | elif w == -1:
29 | print(data)
30 | raise Exception('sodium test failed')
31 |
32 |
33 |
34 | print('-----------测试:子线程----------')
35 | t = threading.Thread(target=test2)
36 | t.start()
37 | t.join()
38 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from setuptools import setup
4 |
5 | requirements = list()
6 | with open('requirements.txt') as f:
7 | requirements = f.read().splitlines()
8 |
9 | dependencies = list()
10 | with open('dependencies.txt') as f:
11 | requirements = f.read().splitlines()
12 |
13 | readme = '\n'.join(open('README.md').readlines())
14 |
15 | setup(
16 | name='ssshare',
17 | version='1.1.2',
18 |
19 | # Project description
20 | description='Crawl ShadowSocksR(SSR) accounts, sharing them on the web, and supporting subscription.',
21 | long_description=readme,
22 | long_description_content_type="text/markdown",
23 |
24 | # Author details
25 | author='Charles Xu',
26 | author_email='charl3s.xu@gmail.com',
27 |
28 | # Project details
29 | url='https://github.com/the0demiurge/ShadowSocksShare',
30 |
31 | # Project dependencies
32 | dependency_links=dependencies,
33 | install_requires=requirements,
34 | classifiers=[
35 | "Programming Language :: Python :: 3",
36 | "License :: OSI Approved :: Apache Software License",
37 | "Operating System :: POSIX",
38 | ],
39 | )
40 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/lib/libsodium.la:
--------------------------------------------------------------------------------
1 | # libsodium.la - a libtool library file
2 | # Generated by libtool (GNU libtool) 2.4.6
3 | #
4 | # Please DO NOT delete this file!
5 | # It is necessary for linking the library.
6 |
7 | # The name that we can dlopen(3).
8 | dlname='libsodium.so.23'
9 |
10 | # Names of this library.
11 | library_names='libsodium.so.23.0.0 libsodium.so.23 libsodium.so'
12 |
13 | # The name of the static archive.
14 | old_library='libsodium.a'
15 |
16 | # Linker flags that cannot go in dependency_libs.
17 | inherited_linker_flags=' -pthread'
18 |
19 | # Libraries that this one depends upon.
20 | dependency_libs=''
21 |
22 | # Names of additional weak libraries provided by this library
23 | weak_library_names=''
24 |
25 | # Version information for libsodium.
26 | current=23
27 | age=0
28 | revision=0
29 |
30 | # Is this an already installed library?
31 | installed=yes
32 |
33 | # Should we warn about portability when linking against -modules?
34 | shouldnotlink=no
35 |
36 | # Files to dlopen/dlpreopen
37 | dlopen=''
38 | dlpreopen=''
39 |
40 | # Directory that this library needs to be installed in:
41 | libdir='/tmp/build_da28d797fa3200ed2b83d92a7b6c8796/the0demiurge-ShadowSocksShare-a92f662/vendor/libsodium/lib'
42 |
--------------------------------------------------------------------------------
/ssshare/templates/full.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %}免费ShadowSocks账号分享{% endblock %}
3 |
4 | {% block content %}
5 |
6 |
7 | 全部SS账号信息(不能保证全都能用)
8 |
9 |
10 | 返回主页
11 |
12 | 必读信息
13 |
14 | 请使用 ShadowSocksR 客户端,不然无法使用。客户端链接在此
15 | 以前的通知信息在此
16 |
17 |
18 |
39 | {% endblock %}
40 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/encrypt_test.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, division, print_function, \
2 | with_statement
3 |
4 | import sys
5 | import os
6 |
7 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
8 |
9 |
10 | from ssshare.shadowsocks.crypto import rc4_md5
11 | from ssshare.shadowsocks.crypto import openssl
12 | from ssshare.shadowsocks.crypto import sodium
13 | from ssshare.shadowsocks.crypto import table
14 |
15 | def run(func):
16 | try:
17 | func()
18 | except:
19 | pass
20 |
21 | def run_n(func, name):
22 | try:
23 | func(name)
24 | except:
25 | pass
26 |
27 | def main():
28 | print("\n""rc4_md5")
29 | rc4_md5.test()
30 | print("\n""aes-256-cfb")
31 | openssl.test_aes_256_cfb()
32 | print("\n""aes-128-cfb")
33 | openssl.test_aes_128_cfb()
34 | print("\n""bf-cfb")
35 | run(openssl.test_bf_cfb)
36 | print("\n""camellia-128-cfb")
37 | run_n(openssl.run_method, "camellia-128-cfb")
38 | print("\n""cast5-cfb")
39 | run_n(openssl.run_method, "cast5-cfb")
40 | print("\n""idea-cfb")
41 | run_n(openssl.run_method, "idea-cfb")
42 | print("\n""seed-cfb")
43 | run_n(openssl.run_method, "seed-cfb")
44 | print("\n""salsa20")
45 | run(sodium.test_salsa20)
46 | print("\n""chacha20")
47 | run(sodium.test_chacha20)
48 |
49 | if __name__ == '__main__':
50 | main()
51 |
--------------------------------------------------------------------------------
/test/test_ssr_check.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from ssshare import *
3 | import threading
4 | from ssshare.ss import crawler, ssr_check
5 | import requests
6 |
7 |
8 | def test2():
9 | for i in range(30):
10 | data = requests.get('http://laptop.pythonic.life:8080/json').text
11 | print('data', i)
12 | data = data.replace('"obfs": "",', '').replace('"protocol_param": "",', '').replace('"obfs_param": "",', '').replace('"protocol": "",', '')
13 | w = ssr_check.test_socks_server(str_json=data)
14 | print('>>>>>>>结果:', w)
15 | if w is True:
16 | print(data)
17 | elif w == -1:
18 | print(data)
19 |
20 |
21 | def test3():
22 | data = crawler.main()
23 | for i in data:
24 | print(i['info'])
25 | for j in i['data']:
26 | w = ssr_check.test_socks_server(str_json=j['json'])
27 | print('>>>>>>>结果:', w)
28 | if w is True:
29 | print(j['json'])
30 | elif w == -1:
31 | print(j['json'])
32 |
33 |
34 | def test4():
35 | data = crawler.main(debug=['no_validate'])
36 | data = ssr_check.validate(data)
37 |
38 | for i in data:
39 | print(i['info'])
40 | for j in i['data']:
41 | print(j['status'])
42 |
43 |
44 | print('-----------测试:子线程----------')
45 | t = threading.Thread(target=test4)
46 | t.start()
47 | t.join()
48 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/rc4_md5.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 clowwindy
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
20 | import hashlib
21 |
22 | from ssshare.shadowsocks.crypto import openssl
23 |
24 | __all__ = ['ciphers']
25 |
26 |
27 | def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None,
28 | i=1, padding=1):
29 | md5 = hashlib.md5()
30 | md5.update(key)
31 | md5.update(iv)
32 | rc4_key = md5.digest()
33 | return openssl.OpenSSLCrypto(b'rc4', rc4_key, b'', op)
34 |
35 |
36 | ciphers = {
37 | 'rc4-md5': (16, 16, create_cipher),
38 | 'rc4-md5-6': (16, 6, create_cipher),
39 | }
40 |
41 |
42 | def test():
43 | from ssshare.shadowsocks.crypto import util
44 |
45 | cipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 1)
46 | decipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 0)
47 |
48 | util.run_cipher(cipher, decipher)
49 |
50 |
51 | if __name__ == '__main__':
52 | test()
53 |
--------------------------------------------------------------------------------
/ssshare/config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import os
4 |
5 | MODE = os.environ.get('MODE', 'strict')
6 | url = [
7 | 'https://fanqiang.network/',
8 | 'https://fanqiang.network/free-shadowsocksr-accounts-page-2',
9 | 'https://fanqiang.network/free-shadowsocksr-accounts-page-3',
10 | 'https://fanqiang.network/free-shadowsocksr-accounts-page-4',
11 | 'https://fanqiang.network/free-shadowsocksr-accounts-page-5',
12 | 'https://github.com/Steve-ShadowsocksR/-SSR-/blob/master/%E5%B0%8F%E9%A3%9E%E6%9C%BA%E7%BA%AF%E5%85%AC%E7%9B%8ASSR%E5%88%86%E4%BA%AB%EF%BC%81',
13 | 'https://lncn.org/',
14 | 'https://nulastudio.org/Freedom/',
15 | 'https://t.me/s/SSRSUB',
16 | 'https://t.me/s/SSRlist',
17 | 'https://t.me/s/gyjclub',
18 | 'https://www.goroutine.me/ssr/index.html',
19 | 'https://viencoding.com/ss-ssr-share',
20 | ]
21 |
22 | subscriptions = [
23 | 'https://heikejilaila.xyz/keji.php?id=c134513fcd69616d4c9fc9fdf4339846',
24 | 'https://muma16fx.netlify.com/',
25 | 'https://qiaomenzhuanfx.netlify.com/',
26 | 'https://raw.githubusercontent.com/eycorsican/rule-sets/master/kitsunebi_sub',
27 | 'https://raw.githubusercontent.com/ssrsub/ssr/master/ssrsub',
28 | 'https://raw.githubusercontent.com/voken100g/AutoSSR/master/online',
29 | 'https://raw.githubusercontent.com/voken100g/AutoSSR/master/recent',
30 | 'https://raw.githubusercontent.com/voken100g/AutoSSR/master/stable',
31 | 'https://teleplusfreessr.000webhostapp.com/',
32 | 'https://www.liesauer.net/yogurt/subscribe?ACCESS_TOKEN=DAYxR3mMaZAsaqUb',
33 | 'https://yangwangssr.000webhostapp.com',
34 | 'https://youlianboshi.netlify.com/',
35 | 'https://yzzz.ml/freessr/',
36 | ]
37 |
--------------------------------------------------------------------------------
/ssshare/js/canvas-nest.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 hustcc
3 | * License: MIT
4 | * Version: v1.0.1
5 | * GitHub: https://github.com/hustcc/canvas-nest.js
6 | **/
7 | !function(){function n(n,e,t){return n.getAttribute(e)||t}function e(n){return document.getElementsByTagName(n)}function t(){var t=e("script"),o=t.length,i=t[o-1];return{l:o,z:n(i,"zIndex",-1),o:n(i,"opacity",.5),c:n(i,"color","0,0,0"),n:n(i,"count",99)}}function o(){a=m.width=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,c=m.height=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight}function i(){r.clearRect(0,0,a,c);var n,e,t,o,m,l;s.forEach(function(i,x){for(i.x+=i.xa,i.y+=i.ya,i.xa*=i.x>a||i.x<0?-1:1,i.ya*=i.y>c||i.y<0?-1:1,r.fillRect(i.x-.5,i.y-.5,1,1),e=x+1;e=n.max/2&&(i.x-=.03*o,i.y-=.03*m),t=(n.max-l)/n.max,r.beginPath(),r.lineWidth=t/2,r.strokeStyle="rgba("+d.c+","+(t+.2)+")",r.moveTo(i.x,i.y),r.lineTo(n.x,n.y),r.stroke()))}),x(i)}var a,c,u,m=document.createElement("canvas"),d=t(),l="c_n"+d.l,r=m.getContext("2d"),x=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(n){window.setTimeout(n,1e3/45)},w=Math.random,y={x:null,y:null,max:2e4};m.id=l,m.style.cssText="position:fixed;top:0;left:0;z-index:"+d.z+";opacity:"+d.o,e("body")[0].appendChild(m),o(),window.onresize=o,window.onmousemove=function(n){n=n||window.event,y.x=n.clientX,y.y=n.clientY},window.onmouseout=function(){y.x=null,y.y=null};for(var s=[],f=0;d.n>f;f++){var h=w()*a,g=w()*c,v=2*w()-1,p=2*w()-1;s.push({x:h,y:g,xa:v,ya:p,max:6e3})}u=s.concat([y]),setTimeout(function(){i()},100)}();
--------------------------------------------------------------------------------
/ssshare/templates/clients.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %}各操作系统的 SSR 客户端地址{% endblock %}
3 |
4 | {% block content %}
5 | 下面是各操作系统的 SSR 客户端地址:
6 |
55 | {% endblock %}
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/python
3 |
4 | ### Python ###
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # C extensions
11 |
12 | # Distribution / packaging
13 | .Python
14 | env/
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *,cover
48 | .hypothesis/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # SageMath parsed files
81 | *.sage.py
82 |
83 | # dotenv
84 | .env
85 |
86 | # virtualenv
87 | .venv
88 | venv/
89 | ENV/
90 |
91 | # Spyder project settings
92 | .spyderproject
93 | .spyproject
94 |
95 | # Rope project settings
96 | .ropeproject
97 |
98 | # mkdocs documentation
99 | /site
100 |
101 | # End of https://www.gitignore.io/api/python
102 | .vscode
103 |
--------------------------------------------------------------------------------
/ssshare/ss/ssr_check.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import requests
3 | import time
4 | import threading
5 | from ssshare.ss import ss_local
6 | import random
7 |
8 |
9 | def test_connection(
10 | url='http://cip.cc',
11 | headers={'User-Agent': 'ShadowSocksShare/web/crawler libcurl/7.21.3 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18'},
12 | proxies=None, port=1080, timeout=10):
13 | if not proxies:
14 | proxies = {'http': 'socks5://localhost:{}'.format(port), 'https': 'socks5://localhost:{}'.format(port)}
15 | ok = False
16 | content = ''
17 | try:
18 | start = time.time()
19 | respond = requests.get(url, headers=headers, proxies=proxies, timeout=timeout)
20 | if respond.ok:
21 | ok = (time.time() - start) * 1000
22 | else:
23 | ok = respond.ok
24 | content = respond.text
25 | except Exception as e:
26 | print(e)
27 | content = repr(e)
28 | return ok, content
29 |
30 |
31 | def test_socks_server(dictionary=None, str_json=None, port=None):
32 | if not port:
33 | port = random.randint(2000, 3000)
34 | try:
35 | try:
36 | loop, tcps, udps = ss_local.main(
37 | dictionary=dictionary, str_json=str_json, port=port)
38 | except Exception as e:
39 | print(e)
40 | return -1, 'SSR start failed'
41 | try:
42 | t = threading.Thread(target=loop.run)
43 | t.start()
44 | time.sleep(3)
45 | conn, content = test_connection(port=port)
46 | loop.stop()
47 | t.join()
48 | tcps.close(next_tick=True)
49 | udps.close(next_tick=True)
50 | time.sleep(1)
51 | return conn, content
52 | except Exception as e:
53 | print(e)
54 | return -2, 'Thread or Connection to website failed'
55 | except SystemExit as e:
56 | return e.code - 10, 'Unknown failure'
57 |
58 |
59 | def validate(websites):
60 | for servers in websites:
61 | print(servers['info'])
62 | for server in servers['data']:
63 | result, info = test_socks_server(str_json=server['json'])
64 | print('>' * 10, '结果:', result)
65 | if result > 0:
66 | print('>' * 10, '测试通过!')
67 | elif result == -1:
68 | print(server['json'])
69 | server['status'] = result
70 | server['content'] = info
71 | return websites
72 |
73 |
74 | if __name__ == '__main__':
75 | print(test_connection())
76 |
--------------------------------------------------------------------------------
/ssshare/ss/ss_local.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2012-2015 clowwindy
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | # not use this file except in compliance with the License. You may obtain
8 | # a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 | # License for the specific language governing permissions and limitations
16 | # under the License.
17 |
18 | from ssshare.shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns
19 |
20 |
21 | def main(dictionary=None, str_json=None, port=None):
22 | shell.check_python()
23 |
24 | # fix py2exe
25 | if str_json:
26 | config = shell.check_and_parse_config(
27 | shell.parse_json_in_str(shell.remove_comment(str_json)))
28 | elif dictionary:
29 | config = shell.check_and_parse_config(dictionary)
30 | else:
31 | raise Exception('No config specified')
32 |
33 | if port:
34 | config['local_port'] = int(port)
35 |
36 | if not config.get('dns_ipv6', False):
37 | asyncdns.IPV6_CONNECTION_SUPPORT = False
38 |
39 | daemon.daemon_exec(config)
40 | # logging.info("local start with protocol[%s] password [%s] method [%s] obfs [%s] obfs_param [%s]" %
41 | # (config['protocol'], config['password'], config['method'], config['obfs'], config['obfs_param']))
42 |
43 | try:
44 | # logging.info("starting local at %s:%d" %
45 | # (config['local_address'], config['local_port']))
46 |
47 | dns_resolver = asyncdns.DNSResolver()
48 | tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)
49 | udp_server = udprelay.UDPRelay(config, dns_resolver, True)
50 | loop = eventloop.EventLoop()
51 | dns_resolver.add_to_loop(loop)
52 | tcp_server.add_to_loop(loop)
53 | udp_server.add_to_loop(loop)
54 |
55 | # daemon.set_user(config.get('user', None))
56 | return [loop, tcp_server, udp_server]
57 | loop.run()
58 | except OSError as e:
59 | print(e)
60 | raise OSError(e)
61 | except Exception as e:
62 | if 'tcp_server' in locals():
63 | tcp_server.close(next_tick=True)
64 | if 'udp_server' in locals():
65 | udp_server.close(next_tick=True)
66 | if 'loop' in locals():
67 | loop.stop()
68 | shell.print_exception(e)
69 | raise Exception(e)
70 |
71 |
72 | if __name__ == '__main__':
73 | pass
74 | # main()
75 |
--------------------------------------------------------------------------------
/ssshare/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% block head %}
6 |
7 |
8 | 免费ShadowSocks账号分享
9 |
10 |
11 |
12 |
13 |
59 | {% endblock %}
60 |
61 |
62 |
63 |
64 |
65 | {% block content %}{% endblock %}
66 |
67 | 如果连上 ssr 之后成功翻了出去,刷新此页面你就能看到下面的微信二维码,并可以资助我^_^
68 |
69 |

72 |
73 |
74 |
75 |
76 |
77 |
78 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/local.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2012-2015 clowwindy
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | # not use this file except in compliance with the License. You may obtain
8 | # a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 | # License for the specific language governing permissions and limitations
16 | # under the License.
17 |
18 | from __future__ import absolute_import, division, print_function, \
19 | with_statement
20 |
21 | import sys
22 | import os
23 | import logging
24 | import signal
25 |
26 | if __name__ == '__main__':
27 | import inspect
28 | file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe())))
29 | sys.path.insert(0, os.path.join(file_path, '../'))
30 |
31 | from ssshare.shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns
32 |
33 |
34 | def main():
35 | shell.check_python()
36 |
37 | # fix py2exe
38 | if hasattr(sys, "frozen") and sys.frozen in \
39 | ("windows_exe", "console_exe"):
40 | p = os.path.dirname(os.path.abspath(sys.executable))
41 | os.chdir(p)
42 |
43 | config = shell.get_config(True)
44 |
45 | if not config.get('dns_ipv6', False):
46 | asyncdns.IPV6_CONNECTION_SUPPORT = False
47 |
48 | daemon.daemon_exec(config)
49 | logging.info("local start with protocol[%s] password [%s] method [%s] obfs [%s] obfs_param [%s]" %
50 | (config['protocol'], config['password'], config['method'], config['obfs'], config['obfs_param']))
51 |
52 | try:
53 | logging.info("starting local at %s:%d" %
54 | (config['local_address'], config['local_port']))
55 |
56 | dns_resolver = asyncdns.DNSResolver()
57 | tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)
58 | udp_server = udprelay.UDPRelay(config, dns_resolver, True)
59 | loop = eventloop.EventLoop()
60 | dns_resolver.add_to_loop(loop)
61 | tcp_server.add_to_loop(loop)
62 | udp_server.add_to_loop(loop)
63 |
64 | def handler(signum, _):
65 | logging.warn('received SIGQUIT, doing graceful shutting down..')
66 | tcp_server.close(next_tick=True)
67 | udp_server.close(next_tick=True)
68 | signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler)
69 |
70 | def int_handler(signum, _):
71 | sys.exit(1)
72 | signal.signal(signal.SIGINT, int_handler)
73 |
74 | daemon.set_user(config.get('user', None))
75 | loop.run()
76 | except Exception as e:
77 | shell.print_exception(e)
78 | sys.exit(1)
79 |
80 | if __name__ == '__main__':
81 | pass
82 | # main()
83 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/obfsplugin/plain.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015-2015 breakwa11
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
20 | import os
21 | import sys
22 | import hashlib
23 | import logging
24 |
25 | from ssshare.shadowsocks.common import ord
26 |
27 | def create_obfs(method):
28 | return plain(method)
29 |
30 | obfs_map = {
31 | 'plain': (create_obfs,),
32 | 'origin': (create_obfs,),
33 | }
34 |
35 | class plain(object):
36 | def __init__(self, method):
37 | self.method = method
38 | self.server_info = None
39 |
40 | def init_data(self):
41 | return b''
42 |
43 | def get_overhead(self, direction): # direction: true for c->s false for s->c
44 | return 0
45 |
46 | def get_server_info(self):
47 | return self.server_info
48 |
49 | def set_server_info(self, server_info):
50 | self.server_info = server_info
51 |
52 | def client_pre_encrypt(self, buf):
53 | return buf
54 |
55 | def client_encode(self, buf):
56 | return buf
57 |
58 | def client_decode(self, buf):
59 | # (buffer_to_recv, is_need_to_encode_and_send_back)
60 | return (buf, False)
61 |
62 | def client_post_decrypt(self, buf):
63 | return buf
64 |
65 | def server_pre_encrypt(self, buf):
66 | return buf
67 |
68 | def server_encode(self, buf):
69 | return buf
70 |
71 | def server_decode(self, buf):
72 | # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back)
73 | return (buf, True, False)
74 |
75 | def server_post_decrypt(self, buf):
76 | return (buf, False)
77 |
78 | def client_udp_pre_encrypt(self, buf):
79 | return buf
80 |
81 | def client_udp_post_decrypt(self, buf):
82 | return buf
83 |
84 | def server_udp_pre_encrypt(self, buf, uid):
85 | return buf
86 |
87 | def server_udp_post_decrypt(self, buf):
88 | return (buf, None)
89 |
90 | def dispose(self):
91 | pass
92 |
93 | def get_head_size(self, buf, def_value):
94 | if len(buf) < 2:
95 | return def_value
96 | head_type = ord(buf[0]) & 0x7
97 | if head_type == 1:
98 | return 7
99 | if head_type == 4:
100 | return 19
101 | if head_type == 3:
102 | return 4 + ord(buf[1])
103 | return def_value
104 |
--------------------------------------------------------------------------------
/ssshare/js/jquery.qrcode.js:
--------------------------------------------------------------------------------
1 | (function( $ ){
2 | $.fn.qrcode = function(options) {
3 | // if options is string,
4 | if( typeof options === 'string' ){
5 | options = { text: options };
6 | }
7 |
8 | // set default values
9 | // typeNumber < 1 for automatic calculation
10 | options = $.extend( {}, {
11 | render : "canvas",
12 | width : 256,
13 | height : 256,
14 | typeNumber : -1,
15 | correctLevel : QRErrorCorrectLevel.H,
16 | background : "#ffffff",
17 | foreground : "#000000"
18 | }, options);
19 |
20 | var createCanvas = function(){
21 | // create the qrcode itself
22 | var qrcode = new QRCode(options.typeNumber, options.correctLevel);
23 | qrcode.addData(options.text);
24 | qrcode.make();
25 |
26 | // create canvas element
27 | var canvas = document.createElement('canvas');
28 | canvas.width = options.width;
29 | canvas.height = options.height;
30 | var ctx = canvas.getContext('2d');
31 |
32 | // compute tileW/tileH based on options.width/options.height
33 | var tileW = options.width / qrcode.getModuleCount();
34 | var tileH = options.height / qrcode.getModuleCount();
35 |
36 | // draw in the canvas
37 | for( var row = 0; row < qrcode.getModuleCount(); row++ ){
38 | for( var col = 0; col < qrcode.getModuleCount(); col++ ){
39 | ctx.fillStyle = qrcode.isDark(row, col) ? options.foreground : options.background;
40 | var w = (Math.ceil((col+1)*tileW) - Math.floor(col*tileW));
41 | var h = (Math.ceil((row+1)*tileW) - Math.floor(row*tileW));
42 | ctx.fillRect(Math.round(col*tileW),Math.round(row*tileH), w, h);
43 | }
44 | }
45 | // return just built canvas
46 | return canvas;
47 | }
48 |
49 | // from Jon-Carlos Rivera (https://github.com/imbcmdth)
50 | var createTable = function(){
51 | // create the qrcode itself
52 | var qrcode = new QRCode(options.typeNumber, options.correctLevel);
53 | qrcode.addData(options.text);
54 | qrcode.make();
55 |
56 | // create table element
57 | var $table = $('')
58 | .css("width", options.width+"px")
59 | .css("height", options.height+"px")
60 | .css("border", "0px")
61 | .css("border-collapse", "collapse")
62 | .css('background-color', options.background);
63 |
64 | // compute tileS percentage
65 | var tileW = options.width / qrcode.getModuleCount();
66 | var tileH = options.height / qrcode.getModuleCount();
67 |
68 | // draw in the table
69 | for(var row = 0; row < qrcode.getModuleCount(); row++ ){
70 | var $row = $('
').css('height', tileH+"px").appendTo($table);
71 |
72 | for(var col = 0; col < qrcode.getModuleCount(); col++ ){
73 | $(' | ')
74 | .css('width', tileW+"px")
75 | .css('background-color', qrcode.isDark(row, col) ? options.foreground : options.background)
76 | .appendTo($row);
77 | }
78 | }
79 | // return just built canvas
80 | return $table;
81 | }
82 |
83 |
84 | return this.each(function(){
85 | var element = options.render == "canvas" ? createCanvas() : createTable();
86 | $(element).appendTo(this);
87 | });
88 | };
89 | })( jQuery );
90 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # 项目贡献指南
2 |
3 | ## 说明
4 |
5 | 本文档的目的在于使有Python爬虫经验的读者在30分钟内熟悉为本项目做出贡献的方法,当原作者不再维护的时候,可以为后续维护者减轻维护门槛与负担。
6 |
7 | ### 1. 本文包含的内容
8 |
9 | 1. 添加新爬虫的方法
10 | 2. 主要接口与配置文件规范
11 | 3. 本项目中设计的常用函数工具及简要文档说明
12 | 4. 为本项目贡献的规范与建议
13 |
14 | # 添加新ss(r)源的方法
15 |
16 | 爬虫代码在[ssshare/ss/crawler.py](https://github.com/the0demiurge/ShadowSocksShare/blob/master/ssshare/ss/crawler.py)中,配置文件在[ssshare/config.py](https://github.com/the0demiurge/ShadowSocksShare/blob/master/ssshare/config.py)中。想添加新的ss(r)源共有三种方法:
17 |
18 | 1. 对于订阅源,在配置文件中添加订阅源地址(见“主要接口与配置文件规范”一节);
19 | 2. 对于简易网页,在配置文件中添加网页的 url(见“主要接口与配置文件规范”一节);
20 | 3. 对于复杂爬虫,需要手动编写爬虫函数。
21 |
22 | ### 爬虫函数编写指南
23 |
24 | 本项目已经为爬虫的编写制作了一些工具,只要在[ssshare/ss/crawler.py](https://github.com/the0demiurge/ShadowSocksShare/blob/master/ssshare/ss/crawler.py)中的`main`函数之前定义一个:
25 |
26 | - 以`crawl_`开头
27 | - 严格按照接口规范返回值
28 | - 不显式指定函数的输入也能正确输出结果
29 |
30 | 的函数,运行网站后就可以自动爬取账号,并将账号于网站上显示。
31 |
32 | ### 临时运行与测试指南
33 |
34 | 简易运行:在项目根目录中运行 ipython 或 python 终端,可以使用 `from ssshare.ss.crawler import *`引入依赖项,然后使用 ipython 或 python 终端测试运行刚写好的程序
35 |
36 | 自动测试:
37 |
38 | - 注释掉[ssshare/config.py](https://github.com/the0demiurge/ShadowSocksShare/blob/master/ssshare/config.py)中所有其他内容
39 | - 将[ssshare/ss/crawler.py](https://github.com/the0demiurge/ShadowSocksShare/blob/master/ssshare/ss/crawler.py)中所有其他以`crawler_`开头的函数名修改为非`crawler_`开头
40 | - 运行`python .travis-test.py`,将运行一遍完整的爬虫和验证ss账号的功能。由于代码运行的时候捕获并打印了所有异常,所以应当阅读终端输出的日志判断爬虫成功还是失败。
41 | - 撤销前两步的修改
42 |
43 | 上线后的日志阅读:
44 |
45 | heroku的dashboard中有个名为“papertrail”的app,点击这个app即可跳转到日志页。在设置中添加两个string filter,`GET`和`HEAD`,之后获取到的日志将只包含网站爬虫和错误的内容。
46 |
47 | # 主要接口与配置文件规范
48 |
49 | ## 配置文件[ssshare/config.py](https://github.com/the0demiurge/ShadowSocksShare/blob/master/ssshare/config.py)
50 |
51 | 配置文件中有2个变量,格式均为装载着网址的list:List[Str]
52 |
53 | - url:简易爬虫的目标网址。该爬虫只将网页源码获取到后检测 `ssr://`、`ss://`链接或图片格式的二维码,无法解析更复杂的网页。
54 | - subscriptions:订阅源
55 |
56 | ## 爬虫函数接口
57 |
58 | ### 函数位置
59 |
60 | 函数应定义或导入于[ssshare/ss/crawler.py](https://github.com/the0demiurge/ShadowSocksShare/blob/master/ssshare/ss/crawler.py)中的`main`函数之前
61 |
62 | ### 函数命名规范
63 |
64 | 应当以`crawl_`开头,主函数将检提取所有符合此规范的函数作为爬虫函数。
65 |
66 | ### 输入参数
67 |
68 | 函数不应当设定任何必填输入参数。
69 |
70 | ### 返回值
71 |
72 | 返回2个参数,按先后顺序分别为:
73 |
74 | - servers
75 | - info
76 |
77 | 其中,**servers ->** List[Dict[Str:Str]]
78 |
79 | servers是一个列表,列表的每个成员为包含ss(r)账号关键信息的字典。字典所有的key和value都应当为字符串。
80 |
81 | 关于ss(r)关键信息的字典说明:
82 |
83 | | key | 是否必填 | 说明 |
84 | | ------------ | -------- | ---------- |
85 | | server | 是 | 服务器地址 |
86 | | server_port | 是 | 服务器端口 |
87 | | password | 是 | 密码 |
88 | | method | 是 | 加密方式 |
89 | | ssr_protocol | 否 | 协议 |
90 | | protoparam | 否 | 协议参数 |
91 | | obfs | 否 | 混淆 |
92 | | obfsparam | 否 | 混淆参数 |
93 | | remarks | 否 | 备注 |
94 |
95 | 来源网站信息**info ->** Dict[Str:Str]
96 |
97 | 来源网站信息是为了使用户能够支持提供免费ss(r)账号而提供的信息。其中包括三个key,均为必填:
98 |
99 | - message:来源网站提供的少量信息,比如什么时候更新的账号,法律或免责信息等
100 | - name:来源网站名称
101 | - url:来源网站网址
102 |
103 | # 本项目中设计的常用函数工具及简要文档说明
104 |
105 | 工具函数都在[ssshare/ss/parse](https://github.com/the0demiurge/ShadowSocksShare/blob/master/ssshare/ss/parse.py)中,使用的时候只需要`from ssshare.ss.parse import 函数名称`即可。
106 |
107 | ## encode, decode
108 |
109 | 输出输入都是str格式,进行urlsafe_base64编码/解码。会自动处理尾部等号。
110 |
111 | ## parse
112 |
113 | 输入 `ssr://`、`ss://`链接,输出储存本条ss账号关键信息的字典。
114 |
115 | ## scanNetQR
116 |
117 | 输入图片地址或base64格式的二维码链接,返回解析结果(字符串)
118 |
119 | # 为本项目贡献的建议
120 |
121 | 1. 尽可能不使用 [requirements.txt](https://github.com/the0demiurge/ShadowSocksShare/blob/master/requirements.txt) 之外安装不够方便的依赖库;
122 | 2. 尽可能不使用需要特别安装的数据库;
123 | 3. 除了对代码行长度80字符的限制可以不遵守外,代码严格按照 Google Python 编程规范执行。
--------------------------------------------------------------------------------
/ssshare/templates/pages.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}{{remarks}}{% endblock %}
4 |
5 | {% block content %}
6 |
7 | {{remarks}}
8 |
9 |
10 | 返回主页
11 |
12 |
13 | {%- if href != 'None' -%}
14 |
15 |
19 |
20 | {% else %}
21 |
22 |
23 |
配置URI
24 |
25 |
26 |
27 |
28 |
29 | SSR二维码
30 |
31 |
32 |
33 |
34 |
35 | 服务器详细配置信息
36 |
37 | | 服务器 |
38 | {{server}} |
39 |
40 |
41 | | 端口 |
42 | {{server_port}} |
43 |
44 |
45 | | 密码 |
46 | {{password}} |
47 |
48 |
49 | | 加密方法 |
50 | {{method}} |
51 |
52 | {%- if ssr_protocol != 'None' -%}
53 |
54 | | SSR协议 |
55 | {{ssr_protocol}} |
56 |
57 | {% endif %}
58 | {%- if obfs != 'None' -%}
59 |
60 | | 混淆方式 |
61 | {{obfs}} |
62 |
63 | {% endif %}
64 | {%- if protoparam != 'None' -%}
65 |
66 | | 协议参数 |
67 | {{protoparam}} |
68 |
69 | {% endif %}
70 | {%- if obfsparam != 'None' -%}
71 |
72 | | 混淆参数 |
73 | {{obfsparam}} |
74 |
75 | {% endif %}
76 |
77 | | 连接状态 |
78 | {{status}} |
79 |
80 |
81 |
IP 归属地
82 |
85 |
86 |
87 |
88 | {%- if json != 'None' -%}
89 |
90 |
JSON格式的配置文件
91 |
94 |
95 |
96 | {% endif %}
97 |
98 | {% endif %}
99 |
100 |
101 |
102 |
103 |
104 |
133 |
134 |
135 |
136 |
137 |
138 |
141 |
142 | {% endblock %}
143 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/obfs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015-2015 breakwa11
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
20 | import os
21 | import sys
22 | import hashlib
23 | import logging
24 |
25 | from ssshare.shadowsocks import common
26 | from ssshare.shadowsocks.obfsplugin import plain, http_simple, obfs_tls, verify, auth, auth_chain
27 |
28 |
29 | method_supported = {}
30 | method_supported.update(plain.obfs_map)
31 | method_supported.update(http_simple.obfs_map)
32 | method_supported.update(obfs_tls.obfs_map)
33 | method_supported.update(verify.obfs_map)
34 | method_supported.update(auth.obfs_map)
35 | method_supported.update(auth_chain.obfs_map)
36 |
37 | def mu_protocol():
38 | return ["auth_aes128_md5", "auth_aes128_sha1", "auth_chain_a"]
39 |
40 | class server_info(object):
41 | def __init__(self, data):
42 | self.data = data
43 |
44 | class obfs(object):
45 | def __init__(self, method):
46 | method = common.to_str(method)
47 | self.method = method
48 | self._method_info = self.get_method_info(method)
49 | if self._method_info:
50 | self.obfs = self.get_obfs(method)
51 | else:
52 | raise Exception('obfs plugin [%s] not supported' % method)
53 |
54 | def init_data(self):
55 | return self.obfs.init_data()
56 |
57 | def set_server_info(self, server_info):
58 | return self.obfs.set_server_info(server_info)
59 |
60 | def get_server_info(self):
61 | return self.obfs.get_server_info()
62 |
63 | def get_method_info(self, method):
64 | method = method.lower()
65 | m = method_supported.get(method)
66 | return m
67 |
68 | def get_obfs(self, method):
69 | m = self._method_info
70 | return m[0](method)
71 |
72 | def get_overhead(self, direction):
73 | return self.obfs.get_overhead(direction)
74 |
75 | def client_pre_encrypt(self, buf):
76 | return self.obfs.client_pre_encrypt(buf)
77 |
78 | def client_encode(self, buf):
79 | return self.obfs.client_encode(buf)
80 |
81 | def client_decode(self, buf):
82 | return self.obfs.client_decode(buf)
83 |
84 | def client_post_decrypt(self, buf):
85 | return self.obfs.client_post_decrypt(buf)
86 |
87 | def server_pre_encrypt(self, buf):
88 | return self.obfs.server_pre_encrypt(buf)
89 |
90 | def server_encode(self, buf):
91 | return self.obfs.server_encode(buf)
92 |
93 | def server_decode(self, buf):
94 | return self.obfs.server_decode(buf)
95 |
96 | def server_post_decrypt(self, buf):
97 | return self.obfs.server_post_decrypt(buf)
98 |
99 | def client_udp_pre_encrypt(self, buf):
100 | return self.obfs.client_udp_pre_encrypt(buf)
101 |
102 | def client_udp_post_decrypt(self, buf):
103 | return self.obfs.client_udp_post_decrypt(buf)
104 |
105 | def server_udp_pre_encrypt(self, buf, uid):
106 | return self.obfs.server_udp_pre_encrypt(buf, uid)
107 |
108 | def server_udp_post_decrypt(self, buf):
109 | return self.obfs.server_udp_post_decrypt(buf)
110 |
111 | def dispose(self):
112 | self.obfs.dispose()
113 | del self.obfs
114 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ╔═╗┬ ┬┌─┐┌┬┐┌─┐┬ ┬╔═╗┌─┐┌─┐┬┌─┌─┐
4 | ╚═╗├─┤├─┤ │││ ││││╚═╗│ ││ ├┴┐└─┐
5 | ╚═╝┴ ┴┴ ┴─┴┘└─┘└┴┘╚═╝└─┘└─┘┴ ┴└─┘
6 |
7 |
8 |
9 | 免责声明
10 |
11 |
12 | 本项目仅进行技术展示,对所爬到的账号不负任何责任。
13 |
14 | 本项目仅面向海外华人用户,中华人民共和国境内居民禁止使用,并请立即关闭本网站!
15 |
16 | 本项目所提供的账号均来自网络,仅供科研、学习之用。
17 |
18 | 请用本项目分享的账号进行学习、科研,切勿用于其他任何用途。
19 |
20 | 请于24小时之内删掉与本项目相关的一切内容,出现一切问题本站作者概不负责。
21 |
22 |
23 |
24 |
25 |
26 |
27 | | master |
28 | dev |
29 |
30 |
31 |  |
32 |  |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | ## 简介:
42 |
43 | 本项目从ss(r)共享网站爬虫获取共享ss(r)账号,通过解析并校验账号连通性,重新分发账号并生成**订阅链接**。
44 |
45 | ***注意事项:我不生产 ss(r) 账号,我只是账号的搬运工。不保证可用,不保证速度,不保证安全,不保证隐私。***
46 |
47 | ### 功能:
48 |
49 | 1. 二维码
50 | 2. ss(r) 分享URL
51 | 3. json 配置
52 | 4. **ssr 订阅和 json 配置订阅**
53 | 5. **每小时自动更新爬虫数据**
54 | 6. 自动检测 ssr 账号可用性
55 |
56 | 示例网站:[ss.pythonic.life](http://ss.pythonic.life)
57 | 备用地址:[ssr.pythonic.life](http://ssr.pythonic.life)
58 |
59 | 博客连接:[the0demiurge.blogspot.jp](https://the0demiurge.blogspot.jp/2017/07/shadowsocks.html)
60 |
61 | ### 为该项目贡献力量:
62 |
63 | 本项目的作者和用户们为您的所有贡献表示由衷的感谢!
64 |
65 | 为本项目添砖加瓦,您可以:
66 |
67 | **以非技术方式:**
68 |
69 | 1. 反馈建议:到[这个页面](https://github.com/the0demiurge/ShadowSocksShare/issues)提交Issue
70 | 2. 提供比较好的 ss(r) 分享链接
71 | 3. 捐助 ssr 账号的来源网站
72 | 4. 捐助我:如果你已经可以上 Google,打开[示例网站](http://ss.pythonic.life)并拖到最后,就能看到微信打赏二维码:)
73 | 5. Fork本项目
74 | 6. 向信得过的人宣传本项目
75 |
76 | **以技术类方式:**
77 |
78 | 1. 阅读[项目贡献指南](https://github.com/the0demiurge/ShadowSocksShare/wiki)并按照项目贡献指南为本项目修改源
79 | 2. 修改项目源码并提交 PR
80 |
81 | ## 用法:
82 |
83 | ### 配置:
84 |
85 | 配置需要运行前设置好环境变量。
86 |
87 | - `MODE`: 设置为 `strict` 后开启严格模式,将会检测robots.txt,如果不允许则不会进行爬虫
88 | - `PORT`: 服务器运行端口
89 |
90 | ### 运行:
91 |
92 | 本地运行:
93 |
94 | `python manage.py runserver`
95 |
96 | 或
97 |
98 | `gunicorn -b :$PORT ssshare.main:app`
99 |
100 | ## Heroku 部署方法:
101 |
102 |
103 | 点击[](https://heroku.com/deploy?template=https://github.com/the0demiurge/ShadowSocksShare-OpenShift/tree/master)一键部署
104 |
105 | 或者参考[**这个网站的教程**](https://hoochanlon.github.io/fq-book/#/web/heroku-deploy)。
106 |
107 | 或者:
108 |
109 | 1. 注册 [Heroku](https://heroku.com)
110 | 2. Fork 本项目
111 | 3. 在[创建应用页面](https://dashboard.heroku.com/new-app)创建一个应用
112 | 4. 在部署 (Deploy) 页面选择 GitHub,在Connect to GitHub 这一栏连接上你的 GitHub 账号,搜索并连接本项目
113 | 5. 在设置(Settings)界面下的 Buildpacks 里面点击 Add buildpack,添加Nodejs,确保buildpack里面同时有`heroku/python`和`heroku/nodejs`两个项目
114 | 6. 在部署 (Deploy) 页面选择一个分支并点击 `Deploy Branch`
115 | 7. 部署完毕后,将网页拉到最上面,并点击`Open app`打开你的网站。注意:网站建立之后会进行爬取并检测账号可用性,大概花费20分钟的时间。
116 |
117 | **部署之后可以选择使用信用卡验证身份,这样可以让你的网站每月在线时间延长。**
118 |
119 | ## Google App Engine 部署方法:
120 | 优点:每月限流量不限时间;缺点:墙内肯定访问不了
121 |
122 | - 进入 [GAE](https://console.cloud.google.com/appengine) 并选择创建一个应用
123 | - 选择 Python 并选择一个地点,按教程打开一个 Google Shell 控制台
124 | - 克隆本项目,输入`git clone https://github.com/the0demiurge/ShadowSocksShare.git`
125 | - 进入项目,分入`cd ShadowSocksShare`
126 | - 输入 `gcloud app deploy app.yaml --project xxx` 部署应用,输入y同意部署。
127 |
128 | 需要注意的是:
129 |
130 | 1. xxx 必须为你的项目名称且必须全部为小写
131 | 2. 必须添加付款方式(信用卡)才能部署,不然会报错
132 |
133 | ```
134 | ERROR: (gcloud.app.deploy) Error [400] Operation does not satisfy the following requirements: billing-enabled {Billing must be enabled for activation of service '' in project 'shadowsocksshare' to proceed., https://console.developers.google.com/project/shadowsocksshare/settings}
135 | ```
136 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/util.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 clowwindy
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
20 | import os
21 | import logging
22 |
23 |
24 | def find_library_nt(name):
25 | # modified from ctypes.util
26 | # ctypes.util.find_library just returns first result he found
27 | # but we want to try them all
28 | # because on Windows, users may have both 32bit and 64bit version installed
29 | results = []
30 | for directory in os.environ['PATH'].split(os.pathsep):
31 | fname = os.path.join(directory, name)
32 | if os.path.isfile(fname):
33 | results.append(fname)
34 | if fname.lower().endswith(".dll"):
35 | continue
36 | fname = fname + ".dll"
37 | if os.path.isfile(fname):
38 | results.append(fname)
39 | return results
40 |
41 |
42 | def find_library(possible_lib_names, search_symbol, library_name):
43 | import ctypes.util
44 | from ctypes import CDLL
45 |
46 | paths = []
47 |
48 | if type(possible_lib_names) not in (list, tuple):
49 | possible_lib_names = [possible_lib_names]
50 |
51 | lib_names = []
52 | for lib_name in possible_lib_names:
53 | lib_names.append(lib_name)
54 | lib_names.append('lib' + lib_name)
55 |
56 | for name in lib_names:
57 | if os.name == "nt":
58 | paths.extend(find_library_nt(name))
59 | else:
60 | path = ctypes.util.find_library(name)
61 | if path:
62 | paths.append(path)
63 |
64 | if not paths:
65 | # We may get here when find_library fails because, for example,
66 | # the user does not have sufficient privileges to access those
67 | # tools underlying find_library on linux.
68 | import glob
69 |
70 | for name in lib_names:
71 | patterns = [
72 | '/usr/local/lib*/lib%s.*' % name,
73 | '/usr/lib*/lib%s.*' % name,
74 | 'lib%s.*' % name,
75 | '%s.dll' % name]
76 |
77 | for pat in patterns:
78 | files = glob.glob(pat)
79 | if files:
80 | paths.extend(files)
81 | for path in paths:
82 | try:
83 | lib = CDLL(path)
84 | if hasattr(lib, search_symbol):
85 | # logging.info('loading %s from %s', library_name, path)
86 | return lib
87 | else:
88 | logging.warn('can\'t find symbol %s in %s', search_symbol,
89 | path)
90 | except Exception:
91 | if path == paths[-1]:
92 | raise
93 | return None
94 |
95 |
96 | def run_cipher(cipher, decipher):
97 | from os import urandom
98 | import random
99 | import time
100 |
101 | BLOCK_SIZE = 16384
102 | rounds = 1 * 1024
103 | plain = urandom(BLOCK_SIZE * rounds)
104 |
105 | results = []
106 | pos = 0
107 | print('test start')
108 | start = time.time()
109 | while pos < len(plain):
110 | l = random.randint(100, 32768)
111 | c = cipher.update(plain[pos:pos + l])
112 | results.append(c)
113 | pos += l
114 | pos = 0
115 | c = b''.join(results)
116 | results = []
117 | while pos < len(plain):
118 | l = random.randint(100, 32768)
119 | results.append(decipher.update(c[pos:pos + l]))
120 | pos += l
121 | end = time.time()
122 | print('speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start)))
123 | assert b''.join(results) == plain
124 |
125 |
126 | def test_find_library():
127 | assert find_library('c', 'strcpy', 'libc') is not None
128 | assert find_library(['c'], 'strcpy', 'libc') is not None
129 | assert find_library(('c',), 'strcpy', 'libc') is not None
130 | assert find_library(('crypto', 'eay32'), 'EVP_CipherUpdate',
131 | 'libcrypto') is not None
132 | assert find_library('notexist', 'strcpy', 'libnotexist') is None
133 | assert find_library('c', 'symbol_not_exist', 'c') is None
134 | assert find_library(('notexist', 'c', 'crypto', 'eay32'),
135 | 'EVP_CipherUpdate', 'libc') is not None
136 |
137 |
138 | if __name__ == '__main__':
139 | test_find_library()
140 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/obfsplugin/verify.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015-2015 breakwa11
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
20 | import os
21 | import sys
22 | import hashlib
23 | import logging
24 | import binascii
25 | import base64
26 | import time
27 | import datetime
28 | import random
29 | import struct
30 | import zlib
31 | import hmac
32 | import hashlib
33 |
34 | import ssshare.shadowsocks
35 | from ssshare.shadowsocks import common
36 | from ssshare.shadowsocks.obfsplugin import plain
37 | from ssshare.shadowsocks.common import to_bytes, to_str, ord, chr
38 |
39 | def create_verify_deflate(method):
40 | return verify_deflate(method)
41 |
42 | obfs_map = {
43 | 'verify_deflate': (create_verify_deflate,),
44 | }
45 |
46 | def match_begin(str1, str2):
47 | if len(str1) >= len(str2):
48 | if str1[:len(str2)] == str2:
49 | return True
50 | return False
51 |
52 | class obfs_verify_data(object):
53 | def __init__(self):
54 | pass
55 |
56 | class verify_base(plain.plain):
57 | def __init__(self, method):
58 | super(verify_base, self).__init__(method)
59 | self.method = method
60 |
61 | def init_data(self):
62 | return obfs_verify_data()
63 |
64 | def set_server_info(self, server_info):
65 | self.server_info = server_info
66 |
67 | def client_encode(self, buf):
68 | return buf
69 |
70 | def client_decode(self, buf):
71 | return (buf, False)
72 |
73 | def server_encode(self, buf):
74 | return buf
75 |
76 | def server_decode(self, buf):
77 | return (buf, True, False)
78 |
79 | class verify_deflate(verify_base):
80 | def __init__(self, method):
81 | super(verify_deflate, self).__init__(method)
82 | self.recv_buf = b''
83 | self.unit_len = 32700
84 | self.decrypt_packet_num = 0
85 | self.raw_trans = False
86 |
87 | def pack_data(self, buf):
88 | if len(buf) == 0:
89 | return b''
90 | data = zlib.compress(buf)
91 | data = struct.pack('>H', len(data)) + data[2:]
92 | return data
93 |
94 | def client_pre_encrypt(self, buf):
95 | ret = b''
96 | while len(buf) > self.unit_len:
97 | ret += self.pack_data(buf[:self.unit_len])
98 | buf = buf[self.unit_len:]
99 | ret += self.pack_data(buf)
100 | return ret
101 |
102 | def client_post_decrypt(self, buf):
103 | if self.raw_trans:
104 | return buf
105 | self.recv_buf += buf
106 | out_buf = b''
107 | while len(self.recv_buf) > 2:
108 | length = struct.unpack('>H', self.recv_buf[:2])[0]
109 | if length >= 32768 or length < 6:
110 | self.raw_trans = True
111 | self.recv_buf = b''
112 | raise Exception('client_post_decrypt data error')
113 | if length > len(self.recv_buf):
114 | break
115 |
116 | out_buf += zlib.decompress(b'x\x9c' + self.recv_buf[2:length])
117 | self.recv_buf = self.recv_buf[length:]
118 |
119 | if out_buf:
120 | self.decrypt_packet_num += 1
121 | return out_buf
122 |
123 | def server_pre_encrypt(self, buf):
124 | ret = b''
125 | while len(buf) > self.unit_len:
126 | ret += self.pack_data(buf[:self.unit_len])
127 | buf = buf[self.unit_len:]
128 | ret += self.pack_data(buf)
129 | return ret
130 |
131 | def server_post_decrypt(self, buf):
132 | if self.raw_trans:
133 | return (buf, False)
134 | self.recv_buf += buf
135 | out_buf = b''
136 | while len(self.recv_buf) > 2:
137 | length = struct.unpack('>H', self.recv_buf[:2])[0]
138 | if length >= 32768 or length < 6:
139 | self.raw_trans = True
140 | self.recv_buf = b''
141 | if self.decrypt_packet_num == 0:
142 | return (b'E'*2048, False)
143 | else:
144 | raise Exception('server_post_decrype data error')
145 | if length > len(self.recv_buf):
146 | break
147 |
148 | out_buf += zlib.decompress(b'\x78\x9c' + self.recv_buf[2:length])
149 | self.recv_buf = self.recv_buf[length:]
150 |
151 | if out_buf:
152 | self.decrypt_packet_num += 1
153 | return (out_buf, False)
154 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/ctypes_libsodium.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright (c) 2014 clowwindy
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | from __future__ import absolute_import, division, print_function, \
24 | with_statement
25 |
26 | import logging
27 | import os
28 | from ctypes import CDLL, c_char_p, c_int, c_ulonglong, byref, \
29 | create_string_buffer, c_void_p
30 |
31 | __all__ = ['ciphers']
32 |
33 | libsodium = None
34 | loaded = False
35 |
36 | buf_size = 2048
37 |
38 | # for salsa20 and chacha20
39 | BLOCK_SIZE = 64
40 | lib_path = os.path.dirname(os.path.realpath(__file__))
41 |
42 |
43 | def load_libsodium():
44 | global loaded, libsodium, buf
45 |
46 | # from ctypes.util import find_library
47 | for p in ('sodium',):
48 | # libsodium_path = find_library(p)
49 | libsodium_path = os.path.join(lib_path, 'lib', 'libsodium.so')
50 | if libsodium_path:
51 | break
52 | if not libsodium_path:
53 | raise Exception('libsodium not found')
54 | logging.info('loading libsodium from %s', libsodium_path)
55 | libsodium = CDLL(libsodium_path)
56 | libsodium.sodium_init.restype = c_int
57 | libsodium.crypto_stream_salsa20_xor_ic.restype = c_int
58 | libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p,
59 | c_ulonglong,
60 | c_char_p, c_ulonglong,
61 | c_char_p)
62 | libsodium.crypto_stream_chacha20_xor_ic.restype = c_int
63 | libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p,
64 | c_ulonglong,
65 | c_char_p, c_ulonglong,
66 | c_char_p)
67 |
68 | libsodium.sodium_init()
69 |
70 | buf = create_string_buffer(buf_size)
71 | loaded = True
72 |
73 |
74 | class Salsa20Crypto(object):
75 |
76 | def __init__(self, cipher_name, key, iv, op):
77 | if not loaded:
78 | load_libsodium()
79 | self.key = key
80 | self.iv = iv
81 | self.key_ptr = c_char_p(key)
82 | self.iv_ptr = c_char_p(iv)
83 | if cipher_name == b'salsa20':
84 | self.cipher = libsodium.crypto_stream_salsa20_xor_ic
85 | elif cipher_name == b'chacha20':
86 | self.cipher = libsodium.crypto_stream_chacha20_xor_ic
87 | else:
88 | raise Exception('Unknown cipher')
89 | # byte counter, not block counter
90 | self.counter = 0
91 |
92 | def update(self, data):
93 | global buf_size, buf
94 | l = len(data)
95 |
96 | # we can only prepend some padding to make the encryption align to
97 | # blocks
98 | padding = self.counter % BLOCK_SIZE
99 | if buf_size < padding + l:
100 | buf_size = (padding + l) * 2
101 | buf = create_string_buffer(buf_size)
102 |
103 | if padding:
104 | data = (b'\0' * padding) + data
105 | self.cipher(byref(buf), c_char_p(data), padding + l,
106 | self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr)
107 | self.counter += l
108 | # buf is copied to a str object when we access buf.raw
109 | # strip off the padding
110 | return buf.raw[padding:padding + l]
111 |
112 |
113 | ciphers = {
114 | b'salsa20': (32, 8, Salsa20Crypto),
115 | b'chacha20': (32, 8, Salsa20Crypto),
116 | }
117 |
118 |
119 | def test_salsa20():
120 | from ssshare.shadowsocks.crypto import util
121 |
122 | cipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 1)
123 | decipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 0)
124 |
125 | util.run_cipher(cipher, decipher)
126 |
127 |
128 | def test_chacha20():
129 | from ssshare.shadowsocks.crypto import util
130 |
131 | cipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 1)
132 | decipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 0)
133 |
134 | util.run_cipher(cipher, decipher)
135 |
136 |
137 | if __name__ == '__main__':
138 | test_chacha20()
139 | test_salsa20()
140 |
--------------------------------------------------------------------------------
/ssshare/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %}免费ShadowSocks账号分享{% endblock %}
3 |
4 | {% block content %}
5 |
6 |
7 |
8 | {{ss}}
9 |
10 |
11 |
12 |
免责声明
13 |
14 | 本网站仅进行技术展示,对所爬到的账号不负任何责任。
15 | 本项目仅面向海外华人用户,中华人民共和国境内居民禁止使用,并请立即关闭本网站!
16 | 本网站所提供的账号均来自网络,仅供科研、学习之用。
17 | 请用本站分享的账号进行学习、科研,切勿用于其他用途。
18 | 请于24小时之内删掉与本站相关的一切内容,否则出现一切问题本站作者概不负责。
19 |
20 |
21 |
22 |
23 |
最新通知
24 |
25 | 由于 Google plus 将于2019年4月2日关闭,而之前爬到的可用账号几乎都来源于 Google plus。
26 | 所以如果大家是自己搭的网站,请时刻关注本项目更新动态,并使用最新源码重新部署。
27 | 最后,如果有推荐的免费ss账号分享网站,不妨在
这里提供一下,或者向作者发email:
charl3s.xu@gmail.com
29 | 作者将在学习工作之余抽空研究并爬一下试试。
30 |
必读信息
31 |
32 | 请使用 ShadowSocksR 客户端,不然无法使用。
33 | 客户端下载地址
34 | 请记住本项目的主网址:ss.pythonic.life
35 | 以及备用网址(将会重定向到解决问题的页面):ssr.pythonic.life
36 | 如果主网址无法访问,可以参考这个 Issue
37 | 获取最新信息,请持续关注本项目的 GitHub 仓库:)
38 | 点这儿查看往期通知信息
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
71 |
72 |
73 |
79 |
80 |
81 |
82 |
83 |
84 | | 作者的博客 |
85 | Blogger (需爬墙) |
86 |
87 |
88 |
89 | | 给作者发 Email 反馈 |
90 | charl3s.xu@gmail.com
91 | |
92 |
93 |
94 |
95 | | 源码地址 |
96 | GitHub 链接 |
97 |
98 |
99 |
102 |
103 |
104 | | 更新源码后访问次数 |
105 | {{counter}} |
106 |
107 |
108 |
109 |
110 | 生日快乐!小宝儿~
111 | 点击查看生日礼物
112 |
113 |
114 | ..................................
115 | . __________________ .
116 | . ( 作者的对象:小胖儿~ ) .
117 | . ------------------ .
118 | . \ .
119 | . \ .
120 | . \ >()_ .
121 | . (__)__ _ ___ __ .
122 | ..................................
123 |
124 |
125 | 留言板
126 |
127 |
128 |
142 |
144 |
150 | {% endblock %}
151 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/sodium.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 clowwindy
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
20 | from ctypes import c_char_p, c_int, c_ulong, c_ulonglong, byref, \
21 | create_string_buffer, c_void_p, CDLL
22 |
23 | from ssshare.shadowsocks.crypto import util
24 |
25 | import os
26 |
27 | lib_path = os.path.dirname(os.path.realpath(__file__))
28 |
29 |
30 | __all__ = ['ciphers']
31 |
32 | libsodium = None
33 | loaded = False
34 |
35 | buf_size = 2048
36 |
37 | # for salsa20 and chacha20 and chacha20-ietf
38 | BLOCK_SIZE = 64
39 |
40 |
41 | def load_libsodium():
42 | global loaded, libsodium, buf
43 |
44 | libsodium = CDLL(os.path.join(lib_path, 'lib', 'libsodium.so'))
45 | # libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic',
46 | # 'libsodium')
47 | if libsodium is None:
48 | raise Exception('libsodium not found')
49 |
50 | libsodium.crypto_stream_salsa20_xor_ic.restype = c_int
51 | libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p,
52 | c_ulonglong,
53 | c_char_p, c_ulonglong,
54 | c_char_p)
55 | libsodium.crypto_stream_chacha20_xor_ic.restype = c_int
56 | libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p,
57 | c_ulonglong,
58 | c_char_p, c_ulonglong,
59 | c_char_p)
60 |
61 | try:
62 | libsodium.crypto_stream_chacha20_ietf_xor_ic.restype = c_int
63 | libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = (c_void_p, c_char_p,
64 | c_ulonglong,
65 | c_char_p, c_ulong,
66 | c_char_p)
67 | except:
68 | pass
69 |
70 | buf = create_string_buffer(buf_size)
71 | loaded = True
72 |
73 |
74 | class SodiumCrypto(object):
75 |
76 | def __init__(self, cipher_name, key, iv, op):
77 | if not loaded:
78 | load_libsodium()
79 | self.key = key
80 | self.iv = iv
81 | self.key_ptr = c_char_p(key)
82 | self.iv_ptr = c_char_p(iv)
83 | if cipher_name == 'salsa20':
84 | self.cipher = libsodium.crypto_stream_salsa20_xor_ic
85 | elif cipher_name == 'chacha20':
86 | self.cipher = libsodium.crypto_stream_chacha20_xor_ic
87 | elif cipher_name == 'chacha20-ietf':
88 | self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic
89 | else:
90 | raise Exception('Unknown cipher')
91 | # byte counter, not block counter
92 | self.counter = 0
93 |
94 | def update(self, data):
95 | global buf_size, buf
96 | l = len(data)
97 |
98 | # we can only prepend some padding to make the encryption align to
99 | # blocks
100 | padding = self.counter % BLOCK_SIZE
101 | if buf_size < padding + l:
102 | buf_size = (padding + l) * 2
103 | buf = create_string_buffer(buf_size)
104 |
105 | if padding:
106 | data = (b'\0' * padding) + data
107 | self.cipher(byref(buf), c_char_p(data), padding + l,
108 | self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr)
109 | self.counter += l
110 | # buf is copied to a str object when we access buf.raw
111 | # strip off the padding
112 | return buf.raw[padding:padding + l]
113 |
114 |
115 | ciphers = {
116 | 'salsa20': (32, 8, SodiumCrypto),
117 | 'chacha20': (32, 8, SodiumCrypto),
118 | 'chacha20-ietf': (32, 12, SodiumCrypto),
119 | }
120 |
121 |
122 | def test_salsa20():
123 | cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1)
124 | decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0)
125 |
126 | util.run_cipher(cipher, decipher)
127 |
128 |
129 | def test_chacha20():
130 |
131 | cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1)
132 | decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0)
133 |
134 | util.run_cipher(cipher, decipher)
135 |
136 |
137 | def test_chacha20_ietf():
138 |
139 | cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1)
140 | decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0)
141 |
142 | util.run_cipher(cipher, decipher)
143 |
144 |
145 | if __name__ == '__main__':
146 | test_chacha20_ietf()
147 | test_chacha20()
148 | test_salsa20()
149 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/lru_cache.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2015 clowwindy
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | # not use this file except in compliance with the License. You may obtain
8 | # a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 | # License for the specific language governing permissions and limitations
16 | # under the License.
17 |
18 | from __future__ import absolute_import, division, print_function, \
19 | with_statement
20 |
21 | import collections
22 | import logging
23 | import time
24 |
25 | if __name__ == '__main__':
26 | import os, sys, inspect
27 | file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe())))
28 | sys.path.insert(0, os.path.join(file_path, '../'))
29 |
30 | try:
31 | from collections import OrderedDict
32 | except:
33 | from ssshare.shadowsocks.ordereddict import OrderedDict
34 |
35 | # this LRUCache is optimized for concurrency, not QPS
36 | # n: concurrency, keys stored in the cache
37 | # m: visits not timed out, proportional to QPS * timeout
38 | # get & set is O(1), not O(n). thus we can support very large n
39 | # sweep is O((n - m)) or O(1024) at most,
40 | # no metter how large the cache or timeout value is
41 |
42 | SWEEP_MAX_ITEMS = 1024
43 |
44 | class LRUCache(collections.MutableMapping):
45 | """This class is not thread safe"""
46 |
47 | def __init__(self, timeout=60, close_callback=None, *args, **kwargs):
48 | self.timeout = timeout
49 | self.close_callback = close_callback
50 | self._store = {}
51 | self._keys_to_last_time = OrderedDict()
52 | self.update(dict(*args, **kwargs)) # use the free update to set keys
53 |
54 | def __getitem__(self, key):
55 | # O(1)
56 | t = time.time()
57 | last_t = self._keys_to_last_time[key]
58 | del self._keys_to_last_time[key]
59 | self._keys_to_last_time[key] = t
60 | return self._store[key]
61 |
62 | def __setitem__(self, key, value):
63 | # O(1)
64 | t = time.time()
65 | if key in self._keys_to_last_time:
66 | del self._keys_to_last_time[key]
67 | self._keys_to_last_time[key] = t
68 | self._store[key] = value
69 |
70 | def __delitem__(self, key):
71 | # O(1)
72 | last_t = self._keys_to_last_time[key]
73 | del self._store[key]
74 | del self._keys_to_last_time[key]
75 |
76 | def __contains__(self, key):
77 | return key in self._store
78 |
79 | def __iter__(self):
80 | return iter(self._store)
81 |
82 | def __len__(self):
83 | return len(self._store)
84 |
85 | def first(self):
86 | if len(self._keys_to_last_time) > 0:
87 | for key in self._keys_to_last_time:
88 | return key
89 |
90 | def sweep(self, sweep_item_cnt = SWEEP_MAX_ITEMS):
91 | # O(n - m)
92 | now = time.time()
93 | c = 0
94 | while c < sweep_item_cnt:
95 | if len(self._keys_to_last_time) == 0:
96 | break
97 | for key in self._keys_to_last_time:
98 | break
99 | last_t = self._keys_to_last_time[key]
100 | if now - last_t <= self.timeout:
101 | break
102 | value = self._store[key]
103 | del self._store[key]
104 | del self._keys_to_last_time[key]
105 | if self.close_callback is not None:
106 | self.close_callback(value)
107 | c += 1
108 | if c:
109 | logging.debug('%d keys swept' % c)
110 | return c < SWEEP_MAX_ITEMS
111 |
112 | def clear(self, keep):
113 | now = time.time()
114 | c = 0
115 | while len(self._keys_to_last_time) > keep:
116 | if len(self._keys_to_last_time) == 0:
117 | break
118 | for key in self._keys_to_last_time:
119 | break
120 | last_t = self._keys_to_last_time[key]
121 | value = self._store[key]
122 | if self.close_callback is not None:
123 | self.close_callback(value)
124 | del self._store[key]
125 | del self._keys_to_last_time[key]
126 | c += 1
127 | if c:
128 | logging.debug('%d keys swept' % c)
129 | return c < SWEEP_MAX_ITEMS
130 |
131 | def test():
132 | c = LRUCache(timeout=0.3)
133 |
134 | c['a'] = 1
135 | assert c['a'] == 1
136 | c['a'] = 1
137 |
138 | time.sleep(0.5)
139 | c.sweep()
140 | assert 'a' not in c
141 |
142 | c['a'] = 2
143 | c['b'] = 3
144 | time.sleep(0.2)
145 | c.sweep()
146 | assert c['a'] == 2
147 | assert c['b'] == 3
148 |
149 | time.sleep(0.2)
150 | c.sweep()
151 | c['b']
152 | time.sleep(0.2)
153 | c.sweep()
154 | assert 'a' not in c
155 | assert c['b'] == 3
156 |
157 | time.sleep(0.5)
158 | c.sweep()
159 | assert 'a' not in c
160 | assert 'b' not in c
161 |
162 | global close_cb_called
163 | close_cb_called = False
164 |
165 | def close_cb(t):
166 | global close_cb_called
167 | assert not close_cb_called
168 | close_cb_called = True
169 |
170 | c = LRUCache(timeout=0.1, close_callback=close_cb)
171 | c['s'] = 1
172 | c['s']
173 | time.sleep(0.1)
174 | c['s']
175 | time.sleep(0.3)
176 | c.sweep()
177 |
178 | if __name__ == '__main__':
179 | test()
180 |
--------------------------------------------------------------------------------
/ssshare/templates/notes.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}全部通知{% endblock %}
4 |
5 | {% block content %}
6 | 全部通知
7 |
10 |
18 |
19 |
24 |
25 |
35 |
36 |
59 |
60 |
61 |
67 |
68 |
69 |
73 |
74 |
75 |
79 |
80 |
81 |
88 |
89 |
90 |
153 | {% endblock %}
154 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/daemon.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2014-2015 clowwindy
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | # not use this file except in compliance with the License. You may obtain
8 | # a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 | # License for the specific language governing permissions and limitations
16 | # under the License.
17 |
18 | from __future__ import absolute_import, division, print_function, \
19 | with_statement
20 |
21 | import os
22 | import sys
23 | import logging
24 | import signal
25 | import time
26 | from ssshare.shadowsocks import common, shell
27 |
28 | # this module is ported from ShadowVPN daemon.c
29 |
30 |
31 | def daemon_exec(config):
32 | if 'daemon' in config:
33 | if os.name != 'posix':
34 | raise Exception('daemon mode is only supported on Unix')
35 | command = config['daemon']
36 | if not command:
37 | command = 'start'
38 | pid_file = config['pid-file']
39 | log_file = config['log-file']
40 | if command == 'start':
41 | daemon_start(pid_file, log_file)
42 | elif command == 'stop':
43 | daemon_stop(pid_file)
44 | # always exit after daemon_stop
45 | sys.exit(0)
46 | elif command == 'restart':
47 | daemon_stop(pid_file)
48 | daemon_start(pid_file, log_file)
49 | else:
50 | raise Exception('unsupported daemon command %s' % command)
51 |
52 |
53 | def write_pid_file(pid_file, pid):
54 | import fcntl
55 | import stat
56 |
57 | try:
58 | fd = os.open(pid_file, os.O_RDWR | os.O_CREAT,
59 | stat.S_IRUSR | stat.S_IWUSR)
60 | except OSError as e:
61 | shell.print_exception(e)
62 | return -1
63 | flags = fcntl.fcntl(fd, fcntl.F_GETFD)
64 | assert flags != -1
65 | flags |= fcntl.FD_CLOEXEC
66 | r = fcntl.fcntl(fd, fcntl.F_SETFD, flags)
67 | assert r != -1
68 | # There is no platform independent way to implement fcntl(fd, F_SETLK, &fl)
69 | # via fcntl.fcntl. So use lockf instead
70 | try:
71 | fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB, 0, 0, os.SEEK_SET)
72 | except IOError:
73 | r = os.read(fd, 32)
74 | if r:
75 | logging.error('already started at pid %s' % common.to_str(r))
76 | else:
77 | logging.error('already started')
78 | os.close(fd)
79 | return -1
80 | os.ftruncate(fd, 0)
81 | os.write(fd, common.to_bytes(str(pid)))
82 | return 0
83 |
84 |
85 | def freopen(f, mode, stream):
86 | oldf = open(f, mode)
87 | oldfd = oldf.fileno()
88 | newfd = stream.fileno()
89 | os.close(newfd)
90 | os.dup2(oldfd, newfd)
91 |
92 |
93 | def daemon_start(pid_file, log_file):
94 |
95 | def handle_exit(signum, _):
96 | if signum == signal.SIGTERM:
97 | sys.exit(0)
98 | sys.exit(1)
99 |
100 | signal.signal(signal.SIGINT, handle_exit)
101 | signal.signal(signal.SIGTERM, handle_exit)
102 |
103 | # fork only once because we are sure parent will exit
104 | pid = os.fork()
105 | assert pid != -1
106 |
107 | if pid > 0:
108 | # parent waits for its child
109 | time.sleep(5)
110 | sys.exit(0)
111 |
112 | # child signals its parent to exit
113 | ppid = os.getppid()
114 | pid = os.getpid()
115 | if write_pid_file(pid_file, pid) != 0:
116 | os.kill(ppid, signal.SIGINT)
117 | sys.exit(1)
118 |
119 | os.setsid()
120 | signal.signal(signal.SIG_IGN, signal.SIGHUP)
121 |
122 | print('started')
123 | os.kill(ppid, signal.SIGTERM)
124 |
125 | sys.stdin.close()
126 | try:
127 | freopen(log_file, 'a', sys.stdout)
128 | freopen(log_file, 'a', sys.stderr)
129 | except IOError as e:
130 | shell.print_exception(e)
131 | sys.exit(1)
132 |
133 |
134 | def daemon_stop(pid_file):
135 | import errno
136 | try:
137 | with open(pid_file) as f:
138 | buf = f.read()
139 | pid = common.to_str(buf)
140 | if not buf:
141 | logging.error('not running')
142 | except IOError as e:
143 | shell.print_exception(e)
144 | if e.errno == errno.ENOENT:
145 | # always exit 0 if we are sure daemon is not running
146 | logging.error('not running')
147 | return
148 | sys.exit(1)
149 | pid = int(pid)
150 | if pid > 0:
151 | try:
152 | os.kill(pid, signal.SIGTERM)
153 | except OSError as e:
154 | if e.errno == errno.ESRCH:
155 | logging.error('not running')
156 | # always exit 0 if we are sure daemon is not running
157 | return
158 | shell.print_exception(e)
159 | sys.exit(1)
160 | else:
161 | logging.error('pid is not positive: %d', pid)
162 |
163 | # sleep for maximum 10s
164 | for i in range(0, 200):
165 | try:
166 | # query for the pid
167 | os.kill(pid, 0)
168 | except OSError as e:
169 | if e.errno == errno.ESRCH:
170 | break
171 | time.sleep(0.05)
172 | else:
173 | logging.error('timed out when stopping pid %d', pid)
174 | sys.exit(1)
175 | print('stopped')
176 | os.unlink(pid_file)
177 |
178 |
179 | def set_user(username):
180 | if username is None:
181 | return
182 |
183 | import pwd
184 | import grp
185 |
186 | try:
187 | pwrec = pwd.getpwnam(username)
188 | except KeyError:
189 | logging.error('user not found: %s' % username)
190 | raise
191 | user = pwrec[0]
192 | uid = pwrec[2]
193 | gid = pwrec[3]
194 |
195 | cur_uid = os.getuid()
196 | if uid == cur_uid:
197 | return
198 | if cur_uid != 0:
199 | logging.error('can not set user as nonroot user')
200 | # will raise later
201 |
202 | # inspired by supervisor
203 | if hasattr(os, 'setgroups'):
204 | groups = [grprec[2] for grprec in grp.getgrall() if user in grprec[3]]
205 | groups.insert(0, gid)
206 | os.setgroups(groups)
207 | os.setgid(gid)
208 | os.setuid(uid)
209 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/ctypes_openssl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright (c) 2014 clowwindy
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | from __future__ import absolute_import, division, print_function, \
24 | with_statement
25 |
26 | import logging
27 | from ctypes import CDLL, c_char_p, c_int, c_long, byref,\
28 | create_string_buffer, c_void_p
29 |
30 | __all__ = ['ciphers']
31 |
32 | libcrypto = None
33 | loaded = False
34 |
35 | buf_size = 2048
36 |
37 |
38 | def load_openssl():
39 | global loaded, libcrypto, buf
40 |
41 | from ctypes.util import find_library
42 | for p in ('crypto', 'eay32', 'libeay32'):
43 | libcrypto_path = find_library(p)
44 | if libcrypto_path:
45 | break
46 | else:
47 | raise Exception('libcrypto(OpenSSL) not found')
48 | logging.info('loading libcrypto from %s', libcrypto_path)
49 | libcrypto = CDLL(libcrypto_path)
50 | libcrypto.EVP_get_cipherbyname.restype = c_void_p
51 | libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p
52 |
53 | libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p,
54 | c_char_p, c_char_p, c_int)
55 |
56 | libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p,
57 | c_char_p, c_int)
58 |
59 | libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
60 | libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,)
61 | if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'):
62 | libcrypto.OpenSSL_add_all_ciphers()
63 |
64 | buf = create_string_buffer(buf_size)
65 | loaded = True
66 |
67 |
68 | def load_cipher(cipher_name):
69 | func_name = b'EVP_' + cipher_name.replace(b'-', b'_')
70 | if bytes != str:
71 | func_name = str(func_name, 'utf-8')
72 | cipher = getattr(libcrypto, func_name, None)
73 | if cipher:
74 | cipher.restype = c_void_p
75 | return cipher()
76 | return None
77 |
78 |
79 | class CtypesCrypto(object):
80 | def __init__(self, cipher_name, key, iv, op):
81 | if not loaded:
82 | load_openssl()
83 | self._ctx = None
84 | cipher = libcrypto.EVP_get_cipherbyname(cipher_name)
85 | if not cipher:
86 | cipher = load_cipher(cipher_name)
87 | if not cipher:
88 | raise Exception('cipher %s not found in libcrypto' % cipher_name)
89 | key_ptr = c_char_p(key)
90 | iv_ptr = c_char_p(iv)
91 | self._ctx = libcrypto.EVP_CIPHER_CTX_new()
92 | if not self._ctx:
93 | raise Exception('can not create cipher context')
94 | r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None,
95 | key_ptr, iv_ptr, c_int(op))
96 | if not r:
97 | self.clean()
98 | raise Exception('can not initialize cipher context')
99 |
100 | def update(self, data):
101 | global buf_size, buf
102 | cipher_out_len = c_long(0)
103 | l = len(data)
104 | if buf_size < l:
105 | buf_size = l * 2
106 | buf = create_string_buffer(buf_size)
107 | libcrypto.EVP_CipherUpdate(self._ctx, byref(buf),
108 | byref(cipher_out_len), c_char_p(data), l)
109 | # buf is copied to a str object when we access buf.raw
110 | return buf.raw[:cipher_out_len.value]
111 |
112 | def __del__(self):
113 | self.clean()
114 |
115 | def clean(self):
116 | if self._ctx:
117 | libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx)
118 | libcrypto.EVP_CIPHER_CTX_free(self._ctx)
119 |
120 |
121 | ciphers = {
122 | b'aes-128-cfb': (16, 16, CtypesCrypto),
123 | b'aes-192-cfb': (24, 16, CtypesCrypto),
124 | b'aes-256-cfb': (32, 16, CtypesCrypto),
125 | b'aes-128-ofb': (16, 16, CtypesCrypto),
126 | b'aes-192-ofb': (24, 16, CtypesCrypto),
127 | b'aes-256-ofb': (32, 16, CtypesCrypto),
128 | b'aes-128-ctr': (16, 16, CtypesCrypto),
129 | b'aes-192-ctr': (24, 16, CtypesCrypto),
130 | b'aes-256-ctr': (32, 16, CtypesCrypto),
131 | b'aes-128-cfb8': (16, 16, CtypesCrypto),
132 | b'aes-192-cfb8': (24, 16, CtypesCrypto),
133 | b'aes-256-cfb8': (32, 16, CtypesCrypto),
134 | b'aes-128-cfb1': (16, 16, CtypesCrypto),
135 | b'aes-192-cfb1': (24, 16, CtypesCrypto),
136 | b'aes-256-cfb1': (32, 16, CtypesCrypto),
137 | b'bf-cfb': (16, 8, CtypesCrypto),
138 | b'camellia-128-cfb': (16, 16, CtypesCrypto),
139 | b'camellia-192-cfb': (24, 16, CtypesCrypto),
140 | b'camellia-256-cfb': (32, 16, CtypesCrypto),
141 | b'cast5-cfb': (16, 8, CtypesCrypto),
142 | b'des-cfb': (8, 8, CtypesCrypto),
143 | b'idea-cfb': (16, 8, CtypesCrypto),
144 | b'rc2-cfb': (16, 8, CtypesCrypto),
145 | b'rc4': (16, 0, CtypesCrypto),
146 | b'seed-cfb': (16, 16, CtypesCrypto),
147 | }
148 |
149 |
150 | def run_method(method):
151 | from ssshare.shadowsocks.crypto import util
152 |
153 | cipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 1)
154 | decipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 0)
155 |
156 | util.run_cipher(cipher, decipher)
157 |
158 |
159 | def test_aes_128_cfb():
160 | run_method(b'aes-128-cfb')
161 |
162 |
163 | def test_aes_256_cfb():
164 | run_method(b'aes-256-cfb')
165 |
166 |
167 | def test_aes_128_cfb8():
168 | run_method(b'aes-128-cfb8')
169 |
170 |
171 | def test_aes_256_ofb():
172 | run_method(b'aes-256-ofb')
173 |
174 |
175 | def test_aes_256_ctr():
176 | run_method(b'aes-256-ctr')
177 |
178 |
179 | def test_bf_cfb():
180 | run_method(b'bf-cfb')
181 |
182 |
183 | def test_rc4():
184 | run_method(b'rc4')
185 |
186 |
187 | if __name__ == '__main__':
188 | test_aes_128_cfb()
189 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/openssl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 clowwindy
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
20 | from ctypes import c_char_p, c_int, c_long, byref,\
21 | create_string_buffer, c_void_p
22 |
23 | from ssshare.shadowsocks import common
24 | from ssshare.shadowsocks.crypto import util
25 |
26 | __all__ = ['ciphers']
27 |
28 | libcrypto = None
29 | loaded = False
30 |
31 | buf_size = 2048
32 |
33 |
34 | def load_openssl():
35 | global loaded, libcrypto, buf
36 |
37 | libcrypto = util.find_library(('crypto', 'eay32'),
38 | 'EVP_get_cipherbyname',
39 | 'libcrypto')
40 | if libcrypto is None:
41 | raise Exception('libcrypto(OpenSSL) not found')
42 |
43 | libcrypto.EVP_get_cipherbyname.restype = c_void_p
44 | libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p
45 |
46 | libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p,
47 | c_char_p, c_char_p, c_int)
48 |
49 | libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p,
50 | c_char_p, c_int)
51 |
52 | if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"):
53 | libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
54 | else:
55 | libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,)
56 | libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,)
57 |
58 | libcrypto.RAND_bytes.restype = c_int
59 | libcrypto.RAND_bytes.argtypes = (c_void_p, c_int)
60 |
61 | if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'):
62 | libcrypto.OpenSSL_add_all_ciphers()
63 |
64 | buf = create_string_buffer(buf_size)
65 | loaded = True
66 |
67 |
68 | def load_cipher(cipher_name):
69 | func_name = 'EVP_' + cipher_name.replace('-', '_')
70 | cipher = getattr(libcrypto, func_name, None)
71 | if cipher:
72 | cipher.restype = c_void_p
73 | return cipher()
74 | return None
75 |
76 | def rand_bytes(length):
77 | if not loaded:
78 | load_openssl()
79 | buf = create_string_buffer(length)
80 | r = libcrypto.RAND_bytes(buf, length)
81 | if r <= 0:
82 | raise Exception('RAND_bytes return error')
83 | return buf.raw
84 |
85 | class OpenSSLCrypto(object):
86 | def __init__(self, cipher_name, key, iv, op):
87 | self._ctx = None
88 | if not loaded:
89 | load_openssl()
90 | cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(cipher_name))
91 | if not cipher:
92 | cipher = load_cipher(cipher_name)
93 | if not cipher:
94 | raise Exception('cipher %s not found in libcrypto' % cipher_name)
95 | key_ptr = c_char_p(key)
96 | iv_ptr = c_char_p(iv)
97 | self._ctx = libcrypto.EVP_CIPHER_CTX_new()
98 | if not self._ctx:
99 | raise Exception('can not create cipher context')
100 | r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None,
101 | key_ptr, iv_ptr, c_int(op))
102 | if not r:
103 | self.clean()
104 | raise Exception('can not initialize cipher context')
105 |
106 | def update(self, data):
107 | global buf_size, buf
108 | cipher_out_len = c_long(0)
109 | l = len(data)
110 | if buf_size < l:
111 | buf_size = l * 2
112 | buf = create_string_buffer(buf_size)
113 | libcrypto.EVP_CipherUpdate(self._ctx, byref(buf),
114 | byref(cipher_out_len), c_char_p(data), l)
115 | # buf is copied to a str object when we access buf.raw
116 | return buf.raw[:cipher_out_len.value]
117 |
118 | def __del__(self):
119 | self.clean()
120 |
121 | def clean(self):
122 | if self._ctx:
123 | if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"):
124 | libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx)
125 | else:
126 | libcrypto.EVP_CIPHER_CTX_reset(self._ctx)
127 | libcrypto.EVP_CIPHER_CTX_free(self._ctx)
128 |
129 |
130 | ciphers = {
131 | 'aes-128-cbc': (16, 16, OpenSSLCrypto),
132 | 'aes-192-cbc': (24, 16, OpenSSLCrypto),
133 | 'aes-256-cbc': (32, 16, OpenSSLCrypto),
134 | 'aes-128-cfb': (16, 16, OpenSSLCrypto),
135 | 'aes-192-cfb': (24, 16, OpenSSLCrypto),
136 | 'aes-256-cfb': (32, 16, OpenSSLCrypto),
137 | 'aes-128-ofb': (16, 16, OpenSSLCrypto),
138 | 'aes-192-ofb': (24, 16, OpenSSLCrypto),
139 | 'aes-256-ofb': (32, 16, OpenSSLCrypto),
140 | 'aes-128-ctr': (16, 16, OpenSSLCrypto),
141 | 'aes-192-ctr': (24, 16, OpenSSLCrypto),
142 | 'aes-256-ctr': (32, 16, OpenSSLCrypto),
143 | 'aes-128-cfb8': (16, 16, OpenSSLCrypto),
144 | 'aes-192-cfb8': (24, 16, OpenSSLCrypto),
145 | 'aes-256-cfb8': (32, 16, OpenSSLCrypto),
146 | 'aes-128-cfb1': (16, 16, OpenSSLCrypto),
147 | 'aes-192-cfb1': (24, 16, OpenSSLCrypto),
148 | 'aes-256-cfb1': (32, 16, OpenSSLCrypto),
149 | 'bf-cfb': (16, 8, OpenSSLCrypto),
150 | 'camellia-128-cfb': (16, 16, OpenSSLCrypto),
151 | 'camellia-192-cfb': (24, 16, OpenSSLCrypto),
152 | 'camellia-256-cfb': (32, 16, OpenSSLCrypto),
153 | 'cast5-cfb': (16, 8, OpenSSLCrypto),
154 | 'des-cfb': (8, 8, OpenSSLCrypto),
155 | 'idea-cfb': (16, 8, OpenSSLCrypto),
156 | 'rc2-cfb': (16, 8, OpenSSLCrypto),
157 | 'rc4': (16, 0, OpenSSLCrypto),
158 | 'seed-cfb': (16, 16, OpenSSLCrypto),
159 | }
160 |
161 |
162 | def run_method(method):
163 |
164 | cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1)
165 | decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0)
166 |
167 | util.run_cipher(cipher, decipher)
168 |
169 |
170 | def test_aes_128_cfb():
171 | run_method('aes-128-cfb')
172 |
173 |
174 | def test_aes_256_cfb():
175 | run_method('aes-256-cfb')
176 |
177 |
178 | def test_aes_128_cfb8():
179 | run_method('aes-128-cfb8')
180 |
181 |
182 | def test_aes_256_ofb():
183 | run_method('aes-256-ofb')
184 |
185 |
186 | def test_aes_256_ctr():
187 | run_method('aes-256-ctr')
188 |
189 |
190 | def test_bf_cfb():
191 | run_method('bf-cfb')
192 |
193 |
194 | def test_rc4():
195 | run_method('rc4')
196 |
197 |
198 | if __name__ == '__main__':
199 | test_aes_128_cfb()
200 |
--------------------------------------------------------------------------------
/ssshare/views.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import base64
4 | import random
5 | import logging
6 | import time
7 | import threading
8 | import os
9 | from ssshare import app
10 | from ssshare.ascii import birthday_2017, ss_title
11 | from ssshare.ss import crawler
12 | from ssshare import donation
13 | from flask import render_template, send_from_directory, abort
14 | from apscheduler.schedulers.background import BackgroundScheduler
15 |
16 |
17 | # Config for disqus comment board
18 | DOMAIN = 'http://ss.pythonic.life'
19 | DISQUS_IDENTIFIER = 'shadowsocksshare'
20 |
21 |
22 | servers = [{'data': [], 'info': {'message': '别着急,正在爬数据,十分钟后再回来吧:)', 'url': 'http://ss.pythonic.life', 'name': '免费 ShadowSocks 账号分享'}}]
23 | curtime = time.ctime()
24 |
25 | encoded = ''
26 | full_encoded = ''
27 | jsons = list()
28 | full_jsons = list()
29 | scheduler = BackgroundScheduler()
30 |
31 |
32 | def update_servers():
33 | try:
34 | # servers
35 | global servers
36 | servers = crawler.main()
37 | # subscription
38 | global encoded
39 | global full_encoded
40 | global jsons
41 | global full_jsons
42 | jsons = list()
43 | decoded = list()
44 | full_decoded = list()
45 | for website in servers:
46 | for server in website['data']:
47 | full_decoded.append(server['ssr_uri'])
48 | full_jsons.append(server['json'])
49 | if server['status'] > 0:
50 | decoded.append(server['ssr_uri'])
51 | jsons.append(server['json'])
52 |
53 | decoded = '\n'.join(decoded)
54 | encoded = base64.urlsafe_b64encode(bytes(decoded, 'utf-8'))
55 | full_decoded = '\n'.join(full_decoded)
56 | full_encoded = base64.urlsafe_b64encode(bytes(full_decoded, 'utf-8'))
57 | time.sleep(7200)
58 | except Exception as e:
59 | logging.exception(e, stack_info=True)
60 |
61 |
62 | # counter_path = os.path.expanduser('/tmp/counter')
63 | counter_path = 'memory'
64 | count = 0
65 |
66 |
67 | def counter(counter_path=counter_path, update=True):
68 | if update:
69 | if counter_path == 'memory':
70 | global count
71 | count += 1
72 | else:
73 | if not os.path.exists(os.path.split(counter_path)[0]):
74 | os.makedirs(os.path.split(counter_path)[0])
75 | if not os.path.exists(counter_path):
76 | open(counter_path, 'w').write('0')
77 | count = int(open(counter_path).readline())
78 | open(counter_path, 'w').write(str(count + 1))
79 | return count
80 |
81 |
82 | @app.route('/')
83 | def index():
84 | try:
85 | return render_template(
86 | 'index.html',
87 | servers=servers,
88 | ss=ss_title[random.randint(0, len(ss_title) - 1)],
89 | counter=counter(),
90 | ctime=curtime,
91 | PAGE_URL=DOMAIN + '/',
92 | PAGE_IDENTIFIER='shadowsocksshare'
93 | )
94 | except Exception as e:
95 | logging.exception(e, stack_info=True)
96 |
97 |
98 | @app.route('/full')
99 | def full():
100 | try:
101 | return render_template(
102 | 'full.html',
103 | servers=servers,
104 | ss=ss_title[random.randint(0, len(ss_title) - 1)],
105 | counter=counter(),
106 | ctime=curtime,
107 | )
108 | except Exception as e:
109 | logging.exception(e, stack_info=True)
110 |
111 |
112 | @app.route('/')
113 | def pages(path):
114 | print(path)
115 | try:
116 | a, b = path.split('-')
117 | a, b = int(a), int(b)
118 | except Exception:
119 | abort(404)
120 |
121 | if a >= len(servers):
122 | abort(404)
123 | elif b >= len(servers[a]['data']):
124 | abort(404)
125 |
126 | try:
127 | uri = servers[a]['data'][b].get('decoded_url', '')
128 | remarks = servers[a]['data'][b].get('remarks', 'None')
129 | server = servers[a]['data'][b].get('server', 'None')
130 | server_port = servers[a]['data'][b].get('server_port', 'None')
131 | password = servers[a]['data'][b].get('password', 'None')
132 | method = servers[a]['data'][b].get('method', 'None')
133 | ssr_protocol = servers[a]['data'][b].get('ssr_protocol', 'None')
134 | obfs = servers[a]['data'][b].get('obfs', 'None')
135 | href = servers[a]['data'][b].get('href', 'None')
136 | json = servers[a]['data'][b].get('json', 'None')
137 | obfsparam = servers[a]['data'][b].get('obfsparam', 'None')
138 | protoparam = servers[a]['data'][b].get('protoparam', 'None')
139 | status = servers[a]['data'][b].get('status', 'None')
140 | content = servers[a]['data'][b].get('content', 'None')
141 |
142 | return render_template(
143 | 'pages.html',
144 | uri=uri,
145 | server=server,
146 | server_port=server_port,
147 | password=password,
148 | method=method,
149 | ssr_protocol=ssr_protocol,
150 | obfs=obfs,
151 | href=href,
152 | remarks=remarks,
153 | counter=counter(),
154 | server_data=servers[a]['data'][b],
155 | json=json,
156 | obfsparam=obfsparam,
157 | protoparam=protoparam,
158 | status=status,
159 | content=content,
160 | )
161 | except Exception as e:
162 | logging.exception(e, stack_info=True)
163 |
164 |
165 | @app.route('/html/')
166 | def static_html(path):
167 | try:
168 | return render_template(path, )
169 | except Exception as e:
170 | logging.exception(e)
171 | abort(404)
172 |
173 |
174 | @app.route('/donation')
175 | def html_donation():
176 | try:
177 | return render_template(
178 | 'donate.html',
179 | data=donation.data,
180 | sum_people=donation.sum_people,
181 | sum_money=donation.sum_money,
182 | )
183 | except Exception as e:
184 | logging.exception(e)
185 | abort(404)
186 |
187 |
188 | @app.route('/subscribe')
189 | def subscribe():
190 | counter('', False)
191 | return encoded
192 |
193 |
194 | @app.route('/full/subscribe')
195 | def full_subscribe():
196 | counter('', False)
197 | return full_encoded
198 |
199 |
200 | @app.route('/json')
201 | def subscribe_json():
202 | counter('', False)
203 | return '{}' if len(jsons) == 0 else random.sample(jsons, 1)[0]
204 |
205 |
206 | @app.route('/full/json')
207 | def full_subscribe_json():
208 | counter('', False)
209 | return '{}' if len(jsons) == 0 else random.sample(full_jsons, 1)[0]
210 |
211 |
212 | @app.route('/js/')
213 | def send_jsadfsadfs(path):
214 | return send_from_directory('js', path)
215 |
216 |
217 | @app.route('/static/')
218 | def send_static(path):
219 | return send_from_directory('static', path)
220 |
221 |
222 | @app.route('/favicon.ico')
223 | def send_favicon():
224 | return send_from_directory('static', 'favicon.ico')
225 |
226 |
227 | @app.errorhandler(404)
228 | def page_not_found(e):
229 | return render_template('404.html', ), 404
230 |
231 |
232 | @app.route('/gift')
233 | def gift():
234 | return birthday_2017
235 |
236 |
237 | def start():
238 | update_thread = threading.Thread(target=update_servers)
239 | scheduler.add_job(update_servers, "cron", minute=random.randint(1, 15), second=random.randint(0, 59))
240 | update_thread.start()
241 | scheduler.start()
242 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/encrypt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2012-2015 clowwindy
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
20 | import os
21 | import sys
22 | import hashlib
23 | import logging
24 |
25 | from ssshare.shadowsocks import common
26 | from ssshare.shadowsocks.crypto import rc4_md5, openssl, sodium, table
27 |
28 |
29 | method_supported = {}
30 | method_supported.update(rc4_md5.ciphers)
31 | method_supported.update(openssl.ciphers)
32 | method_supported.update(sodium.ciphers)
33 | method_supported.update(table.ciphers)
34 |
35 |
36 | def random_string(length):
37 | try:
38 | return os.urandom(length)
39 | except NotImplementedError as e:
40 | return openssl.rand_bytes(length)
41 |
42 | cached_keys = {}
43 |
44 |
45 | def try_cipher(key, method=None):
46 | Encryptor(key, method)
47 |
48 |
49 | def EVP_BytesToKey(password, key_len, iv_len):
50 | # equivalent to OpenSSL's EVP_BytesToKey() with count 1
51 | # so that we make the same key and iv as nodejs version
52 | if hasattr(password, 'encode'):
53 | password = password.encode('utf-8')
54 | cached_key = '%s-%d-%d' % (password, key_len, iv_len)
55 | r = cached_keys.get(cached_key, None)
56 | if r:
57 | return r
58 | m = []
59 | i = 0
60 | while len(b''.join(m)) < (key_len + iv_len):
61 | md5 = hashlib.md5()
62 | data = password
63 | if i > 0:
64 | data = m[i - 1] + password
65 | md5.update(data)
66 | m.append(md5.digest())
67 | i += 1
68 | ms = b''.join(m)
69 | key = ms[:key_len]
70 | iv = ms[key_len:key_len + iv_len]
71 | cached_keys[cached_key] = (key, iv)
72 | return key, iv
73 |
74 |
75 | class Encryptor(object):
76 | def __init__(self, key, method, iv = None):
77 | self.key = key
78 | self.method = method
79 | self.iv = None
80 | self.iv_sent = False
81 | self.cipher_iv = b''
82 | self.iv_buf = b''
83 | self.cipher_key = b''
84 | self.decipher = None
85 | method = method.lower()
86 | self._method_info = self.get_method_info(method)
87 | if self._method_info:
88 | if iv is None or len(iv) != self._method_info[1]:
89 | self.cipher = self.get_cipher(key, method, 1,
90 | random_string(self._method_info[1]))
91 | else:
92 | self.cipher = self.get_cipher(key, method, 1, iv)
93 | else:
94 | logging.error('method %s not supported' % method)
95 | sys.exit(1)
96 |
97 | def get_method_info(self, method):
98 | method = method.lower()
99 | m = method_supported.get(method)
100 | return m
101 |
102 | def iv_len(self):
103 | return len(self.cipher_iv)
104 |
105 | def get_cipher(self, password, method, op, iv):
106 | password = common.to_bytes(password)
107 | m = self._method_info
108 | if m[0] > 0:
109 | key, iv_ = EVP_BytesToKey(password, m[0], m[1])
110 | else:
111 | # key_length == 0 indicates we should use the key directly
112 | key, iv = password, b''
113 |
114 | iv = iv[:m[1]]
115 | if op == 1:
116 | # this iv is for cipher not decipher
117 | self.cipher_iv = iv[:m[1]]
118 | self.cipher_key = key
119 | return m[2](method, key, iv, op)
120 |
121 | def encrypt(self, buf):
122 | if len(buf) == 0:
123 | return buf
124 | if self.iv_sent:
125 | return self.cipher.update(buf)
126 | else:
127 | self.iv_sent = True
128 | return self.cipher_iv + self.cipher.update(buf)
129 |
130 | def decrypt(self, buf):
131 | if len(buf) == 0:
132 | return buf
133 | if self.decipher is not None: #optimize
134 | return self.decipher.update(buf)
135 |
136 | decipher_iv_len = self._method_info[1]
137 | if len(self.iv_buf) <= decipher_iv_len:
138 | self.iv_buf += buf
139 | if len(self.iv_buf) > decipher_iv_len:
140 | decipher_iv = self.iv_buf[:decipher_iv_len]
141 | self.decipher = self.get_cipher(self.key, self.method, 0,
142 | iv=decipher_iv)
143 | buf = self.iv_buf[decipher_iv_len:]
144 | del self.iv_buf
145 | return self.decipher.update(buf)
146 | else:
147 | return b''
148 |
149 | def encrypt_all(password, method, op, data):
150 | result = []
151 | method = method.lower()
152 | (key_len, iv_len, m) = method_supported[method]
153 | if key_len > 0:
154 | key, _ = EVP_BytesToKey(password, key_len, iv_len)
155 | else:
156 | key = password
157 | if op:
158 | iv = random_string(iv_len)
159 | result.append(iv)
160 | else:
161 | iv = data[:iv_len]
162 | data = data[iv_len:]
163 | cipher = m(method, key, iv, op)
164 | result.append(cipher.update(data))
165 | return b''.join(result)
166 |
167 | def encrypt_key(password, method):
168 | method = method.lower()
169 | (key_len, iv_len, m) = method_supported[method]
170 | if key_len > 0:
171 | key, _ = EVP_BytesToKey(password, key_len, iv_len)
172 | else:
173 | key = password
174 | return key
175 |
176 | def encrypt_iv_len(method):
177 | method = method.lower()
178 | (key_len, iv_len, m) = method_supported[method]
179 | return iv_len
180 |
181 | def encrypt_new_iv(method):
182 | method = method.lower()
183 | (key_len, iv_len, m) = method_supported[method]
184 | return random_string(iv_len)
185 |
186 | def encrypt_all_iv(key, method, op, data, ref_iv):
187 | result = []
188 | method = method.lower()
189 | (key_len, iv_len, m) = method_supported[method]
190 | if op:
191 | iv = ref_iv[0]
192 | result.append(iv)
193 | else:
194 | iv = data[:iv_len]
195 | data = data[iv_len:]
196 | ref_iv[0] = iv
197 | cipher = m(method, key, iv, op)
198 | result.append(cipher.update(data))
199 | return b''.join(result)
200 |
201 |
202 | CIPHERS_TO_TEST = [
203 | 'aes-128-cfb',
204 | 'aes-256-cfb',
205 | 'rc4-md5',
206 | 'salsa20',
207 | 'chacha20',
208 | 'table',
209 | ]
210 |
211 |
212 | def test_encryptor():
213 | from os import urandom
214 | plain = urandom(10240)
215 | for method in CIPHERS_TO_TEST:
216 | logging.warn(method)
217 | encryptor = Encryptor(b'key', method)
218 | decryptor = Encryptor(b'key', method)
219 | cipher = encryptor.encrypt(plain)
220 | plain2 = decryptor.decrypt(cipher)
221 | assert plain == plain2
222 |
223 |
224 | def test_encrypt_all():
225 | from os import urandom
226 | plain = urandom(10240)
227 | for method in CIPHERS_TO_TEST:
228 | logging.warn(method)
229 | cipher = encrypt_all(b'key', method, 1, plain)
230 | plain2 = encrypt_all(b'key', method, 0, cipher)
231 | assert plain == plain2
232 |
233 |
234 | if __name__ == '__main__':
235 | test_encrypt_all()
236 | test_encryptor()
237 |
--------------------------------------------------------------------------------
/ssshare/ss/parse.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import re
4 | import zbar
5 | import requests
6 | from PIL import Image
7 | from io import BytesIO
8 | from numpy import array, uint8
9 | import base64
10 | import urllib
11 | import json
12 | import logging
13 |
14 |
15 | scanner = zbar.Scanner()
16 |
17 |
18 | def decode(string):
19 | try:
20 | return str(
21 | base64.urlsafe_b64decode(
22 | bytes(
23 | string.strip('/') + (4 - len(string.strip('/')) % 4) * '=' + '====',
24 | 'utf-8')), 'utf-8')
25 | except Exception as e:
26 | print(e, string)
27 | raise Exception(e, string)
28 |
29 |
30 | def encode(decoded):
31 | return base64.urlsafe_b64encode(
32 | bytes(str(decoded), 'utf-8')).decode('utf-8').replace('=', '')
33 |
34 |
35 | def parse(uri, default_title='untitled'):
36 | server = dict()
37 | stripped = re.sub('ssr?://', '', uri)
38 | if uri[2] == ':':
39 | # ss
40 | if '#' in uri:
41 | stripped, remarks = stripped.split('#')[:2]
42 | server['remarks'] = urllib.parse.unquote(remarks)
43 | else:
44 | server['remarks'] = default_title
45 | decoded = decode(stripped)
46 | data = decoded.split('@', maxsplit=1)
47 | server['method'], server['password'] = data[0].split(':', maxsplit=1)
48 | server['server'], server['server_port'] = data[1].rsplit(':', maxsplit=1)
49 | elif uri[2] == 'r':
50 | # ssr
51 | decoded = decode(stripped)
52 | data = decoded.split('/?')
53 | [
54 | server['server'],
55 | server['server_port'],
56 | server['ssr_protocol'],
57 | server['method'],
58 | server['obfs'],
59 | password_enc,
60 | ] = data[0].rsplit(':', maxsplit=5)
61 | server['password'] = decode(password_enc)
62 | server['remarks'] = default_title
63 | if len(data) > 1:
64 | appendix = data[1].split('&')
65 | content = {i.split('=')[0]: i.split('=')[1] for i in appendix}
66 | for key in content:
67 | server[key] = decode(content[key])
68 | if server['ssr_protocol'] != 'origin' and server['obfs'] != 'plain':
69 | server['remarks'] += ' SSR'
70 | return server
71 |
72 |
73 | def scanNetQR(img_url, headers=None):
74 |
75 | if img_url.startswith('http'):
76 | img_bytes = requests.get(img_url, headers=headers).content
77 | elif img_url.startswith('data:image'):
78 | img_bytes = base64.decodebytes(bytes(img_url.split(',')[1], 'utf-8'))
79 | img = array(Image.open(BytesIO(img_bytes)))
80 | info = scanner.scan(img.astype(uint8) * 255) + scanner.scan((1 - img).astype(uint8) * 255)
81 | if len(info) == 0:
82 | raise ValueError('scanner fail to identify qr code')
83 | return info[0].data.decode('utf-8')
84 |
85 |
86 | def get_href(string, pattern='.*'):
87 | found = re.findall(r'(?<=%s)' % pattern, string)
88 | if found:
89 | return found[0]
90 |
91 |
92 | def gen_uri(servers):
93 | '''{
94 | "server": server['server'],
95 | "server_ipv6": "::",
96 | "server_port": int(server['server_port']),
97 | "local_address": "127.0.0.1",
98 | "local_port": 1080,
99 | "password": server['password'],
100 | "timeout": 300,
101 | "udp_timeout": 60,
102 | "method": method,
103 | "protocol": ssr_protocol,
104 | "protocol_param": "",
105 | "obfs": obfs,
106 | "obfs_param": "",
107 | "fast_open": False,
108 | "workers": 1,
109 | "group": "ss.pythonic.life"
110 | },'''
111 |
112 | result_servers = list()
113 | for server in servers:
114 | if 'password' not in server:
115 | server['password'] = ''
116 | try:
117 | for key in ['method', 'password', 'server', 'server_port']:
118 | assert key in server, '{key} not in server data'.format(key)
119 | for k, v in (('ssr_protocol', 'origin'), ('obfs', 'plain')):
120 | if k in server and server[k] == v:
121 | server.pop(k)
122 |
123 | is_ss = 'ssr_protocol' not in server and 'obfs' not in server
124 |
125 | if is_ss:
126 | # if not completed, it's ss
127 | decoded = '{method}:{password}@{hostname}:{port}'.format(
128 | method=server['method'],
129 | password=server['password'],
130 | hostname=server['server'],
131 | port=server['server_port'],
132 | )
133 | ss_uri = 'ss://{}#{}'.format(
134 | str(base64.urlsafe_b64encode(bytes(decoded, encoding='utf8')), encoding='utf-8'),
135 | urllib.parse.quote(server['remarks'])
136 | )
137 |
138 | # ssr formatted account info
139 | ssr_decoded = ':'.join([
140 | server['server'],
141 | server['server_port'],
142 | 'origin',
143 | server['method'],
144 | 'plain',
145 | encode(server['password']),
146 | ])
147 | ssr_decoded += '/?remarks={remarks}&group={group}'.format(
148 | remarks=encode(server['remarks']),
149 | group=encode("Charles Xu"),
150 | )
151 |
152 | ssr_uri = 'ssr://{endoced}'.format(
153 | endoced=encode(ssr_decoded)
154 | )
155 | else:
156 |
157 | decoded_head = ':'.join([str(i) for i in [
158 | server['server'],
159 | server['server_port'],
160 | server.get('ssr_protocol', 'origin'),
161 | server['method'],
162 | server.get('obfs', 'plain'),
163 | encode(server['password'])
164 | ]])
165 | appendix = [(key, server[key]) for key in ['obfsparam', 'protoparam', 'remarks'] if key in server]
166 | appendix.append(('group', 'Charles Xu'))
167 | appendix_str = '&'.join(['{key}={val}'.format(
168 | key=item[0],
169 | val=encode(item[1])
170 | ) for item in appendix])
171 | decoded = '/?'.join([decoded_head, appendix_str])
172 |
173 | ss_uri = 'ssr://{endoced}'.format(endoced=encode(decoded))
174 | ssr_uri = ss_uri
175 |
176 | server['uri'] = ss_uri
177 | server['ssr_uri'] = ssr_uri
178 | server['decoded_url'] = urllib.parse.unquote(ss_uri)
179 |
180 | server_data_to_json = {
181 | "server": server['server'],
182 | "server_ipv6": "::",
183 | "server_port": int(server['server_port']),
184 | "local_address": "127.0.0.1",
185 | "local_port": 1080,
186 | "password": server['password'],
187 | "group": "Charles Xu"
188 | }
189 | if 'ssr_protocol' in server:
190 | server['protocol'] = server['ssr_protocol']
191 | for key in ['obfs', 'method', 'protocol', 'obfsparam', 'protoparam', 'udpport', 'uot']:
192 | if key in server:
193 | server_data_to_json[key] = server.get(key)
194 |
195 | server['json'] = json.dumps(
196 | server_data_to_json,
197 | ensure_ascii=False,
198 | indent=2,
199 | )
200 | result_servers.append(server)
201 | except (KeyError, EOFError, ValueError) as e:
202 | logging.exception(e, stack_info=True)
203 |
204 | return result_servers
205 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/eventloop.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2013-2015 clowwindy
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | # not use this file except in compliance with the License. You may obtain
8 | # a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 | # License for the specific language governing permissions and limitations
16 | # under the License.
17 |
18 | # from ssloop
19 | # https://github.com/clowwindy/ssloop
20 |
21 | from __future__ import absolute_import, division, print_function, \
22 | with_statement
23 |
24 | import os
25 | import time
26 | import socket
27 | import select
28 | import errno
29 | import logging
30 | from collections import defaultdict
31 |
32 | from ssshare.shadowsocks import shell
33 |
34 |
35 | __all__ = ['EventLoop', 'POLL_NULL', 'POLL_IN', 'POLL_OUT', 'POLL_ERR',
36 | 'POLL_HUP', 'POLL_NVAL', 'EVENT_NAMES']
37 |
38 | POLL_NULL = 0x00
39 | POLL_IN = 0x01
40 | POLL_OUT = 0x04
41 | POLL_ERR = 0x08
42 | POLL_HUP = 0x10
43 | POLL_NVAL = 0x20
44 |
45 |
46 | EVENT_NAMES = {
47 | POLL_NULL: 'POLL_NULL',
48 | POLL_IN: 'POLL_IN',
49 | POLL_OUT: 'POLL_OUT',
50 | POLL_ERR: 'POLL_ERR',
51 | POLL_HUP: 'POLL_HUP',
52 | POLL_NVAL: 'POLL_NVAL',
53 | }
54 |
55 | # we check timeouts every TIMEOUT_PRECISION seconds
56 | TIMEOUT_PRECISION = 2
57 |
58 |
59 | class KqueueLoop(object):
60 |
61 | MAX_EVENTS = 1024
62 |
63 | def __init__(self):
64 | self._kqueue = select.kqueue()
65 | self._fds = {}
66 |
67 | def _control(self, fd, mode, flags):
68 | events = []
69 | if mode & POLL_IN:
70 | events.append(select.kevent(fd, select.KQ_FILTER_READ, flags))
71 | if mode & POLL_OUT:
72 | events.append(select.kevent(fd, select.KQ_FILTER_WRITE, flags))
73 | for e in events:
74 | self._kqueue.control([e], 0)
75 |
76 | def poll(self, timeout):
77 | if timeout < 0:
78 | timeout = None # kqueue behaviour
79 | events = self._kqueue.control(None, KqueueLoop.MAX_EVENTS, timeout)
80 | results = defaultdict(lambda: POLL_NULL)
81 | for e in events:
82 | fd = e.ident
83 | if e.filter == select.KQ_FILTER_READ:
84 | results[fd] |= POLL_IN
85 | elif e.filter == select.KQ_FILTER_WRITE:
86 | results[fd] |= POLL_OUT
87 | return results.items()
88 |
89 | def register(self, fd, mode):
90 | self._fds[fd] = mode
91 | self._control(fd, mode, select.KQ_EV_ADD)
92 |
93 | def unregister(self, fd):
94 | self._control(fd, self._fds[fd], select.KQ_EV_DELETE)
95 | del self._fds[fd]
96 |
97 | def modify(self, fd, mode):
98 | self.unregister(fd)
99 | self.register(fd, mode)
100 |
101 | def close(self):
102 | self._kqueue.close()
103 |
104 |
105 | class SelectLoop(object):
106 |
107 | def __init__(self):
108 | self._r_list = set()
109 | self._w_list = set()
110 | self._x_list = set()
111 |
112 | def poll(self, timeout):
113 | r, w, x = select.select(self._r_list, self._w_list, self._x_list,
114 | timeout)
115 | results = defaultdict(lambda: POLL_NULL)
116 | for p in [(r, POLL_IN), (w, POLL_OUT), (x, POLL_ERR)]:
117 | for fd in p[0]:
118 | results[fd] |= p[1]
119 | return results.items()
120 |
121 | def register(self, fd, mode):
122 | if mode & POLL_IN:
123 | self._r_list.add(fd)
124 | if mode & POLL_OUT:
125 | self._w_list.add(fd)
126 | if mode & POLL_ERR:
127 | self._x_list.add(fd)
128 |
129 | def unregister(self, fd):
130 | if fd in self._r_list:
131 | self._r_list.remove(fd)
132 | if fd in self._w_list:
133 | self._w_list.remove(fd)
134 | if fd in self._x_list:
135 | self._x_list.remove(fd)
136 |
137 | def modify(self, fd, mode):
138 | self.unregister(fd)
139 | self.register(fd, mode)
140 |
141 | def close(self):
142 | pass
143 |
144 |
145 | class EventLoop(object):
146 | def __init__(self):
147 | if hasattr(select, 'epoll'):
148 | self._impl = select.epoll()
149 | model = 'epoll'
150 | elif hasattr(select, 'kqueue'):
151 | self._impl = KqueueLoop()
152 | model = 'kqueue'
153 | elif hasattr(select, 'select'):
154 | self._impl = SelectLoop()
155 | model = 'select'
156 | else:
157 | raise Exception('can not find any available functions in select '
158 | 'package')
159 | self._fdmap = {} # (f, handler)
160 | self._last_time = time.time()
161 | self._periodic_callbacks = []
162 | self._stopping = False
163 | logging.debug('using event model: %s', model)
164 |
165 | def poll(self, timeout=None):
166 | events = self._impl.poll(timeout)
167 | return [(self._fdmap[fd][0], fd, event) for fd, event in events]
168 |
169 | def add(self, f, mode, handler):
170 | fd = f.fileno()
171 | self._fdmap[fd] = (f, handler)
172 | self._impl.register(fd, mode)
173 |
174 | def remove(self, f):
175 | fd = f.fileno()
176 | del self._fdmap[fd]
177 | self._impl.unregister(fd)
178 |
179 | def removefd(self, fd):
180 | del self._fdmap[fd]
181 | self._impl.unregister(fd)
182 |
183 | def add_periodic(self, callback):
184 | self._periodic_callbacks.append(callback)
185 |
186 | def remove_periodic(self, callback):
187 | self._periodic_callbacks.remove(callback)
188 |
189 | def modify(self, f, mode):
190 | fd = f.fileno()
191 | self._impl.modify(fd, mode)
192 |
193 | def stop(self):
194 | self._stopping = True
195 |
196 | def run(self):
197 | events = []
198 | while not self._stopping:
199 | asap = False
200 | try:
201 | events = self.poll(TIMEOUT_PRECISION)
202 | except (OSError, IOError) as e:
203 | if errno_from_exception(e) in (errno.EPIPE, errno.EINTR):
204 | # EPIPE: Happens when the client closes the connection
205 | # EINTR: Happens when received a signal
206 | # handles them as soon as possible
207 | asap = True
208 | logging.debug('poll:%s', e)
209 | else:
210 | logging.error('poll:%s', e)
211 | import traceback
212 | traceback.print_exc()
213 | continue
214 |
215 | handle = False
216 | for sock, fd, event in events:
217 | handler = self._fdmap.get(fd, None)
218 | if handler is not None:
219 | handler = handler[1]
220 | try:
221 | handle = handler.handle_event(sock, fd, event) or handle
222 | except (OSError, IOError) as e:
223 | shell.print_exception(e)
224 | now = time.time()
225 | if asap or now - self._last_time >= TIMEOUT_PRECISION:
226 | for callback in self._periodic_callbacks:
227 | callback()
228 | self._last_time = now
229 | if events and not handle:
230 | time.sleep(0.001)
231 |
232 | def __del__(self):
233 | self._impl.close()
234 |
235 |
236 | # from tornado
237 | def errno_from_exception(e):
238 | """Provides the errno from an Exception object.
239 |
240 | There are cases that the errno attribute was not set so we pull
241 | the errno out of the args but if someone instantiates an Exception
242 | without any args you will get a tuple error. So this function
243 | abstracts all that behavior to give you a safe way to get the
244 | errno.
245 | """
246 |
247 | if hasattr(e, 'errno'):
248 | return e.errno
249 | elif e.args:
250 | return e.args[0]
251 | else:
252 | return None
253 |
254 |
255 | # from tornado
256 | def get_sock_error(sock):
257 | error_number = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
258 | return socket.error(error_number, os.strerror(error_number))
259 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/ordereddict.py:
--------------------------------------------------------------------------------
1 | import collections
2 |
3 | ################################################################################
4 | ### OrderedDict
5 | ################################################################################
6 |
7 | class OrderedDict(dict):
8 | 'Dictionary that remembers insertion order'
9 | # An inherited dict maps keys to values.
10 | # The inherited dict provides __getitem__, __len__, __contains__, and get.
11 | # The remaining methods are order-aware.
12 | # Big-O running times for all methods are the same as regular dictionaries.
13 |
14 | # The internal self.__map dict maps keys to links in a doubly linked list.
15 | # The circular doubly linked list starts and ends with a sentinel element.
16 | # The sentinel element never gets deleted (this simplifies the algorithm).
17 | # Each link is stored as a list of length three: [PREV, NEXT, KEY].
18 |
19 | def __init__(*args, **kwds):
20 | '''Initialize an ordered dictionary. The signature is the same as
21 | regular dictionaries, but keyword arguments are not recommended because
22 | their insertion order is arbitrary.
23 |
24 | '''
25 | if not args:
26 | raise TypeError("descriptor '__init__' of 'OrderedDict' object "
27 | "needs an argument")
28 | self = args[0]
29 | args = args[1:]
30 | if len(args) > 1:
31 | raise TypeError('expected at most 1 arguments, got %d' % len(args))
32 | try:
33 | self.__root
34 | except AttributeError:
35 | self.__root = root = [] # sentinel node
36 | root[:] = [root, root, None]
37 | self.__map = {}
38 | self.__update(*args, **kwds)
39 |
40 | def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
41 | 'od.__setitem__(i, y) <==> od[i]=y'
42 | # Setting a new item creates a new link at the end of the linked list,
43 | # and the inherited dictionary is updated with the new key/value pair.
44 | if key not in self:
45 | root = self.__root
46 | last = root[0]
47 | last[1] = root[0] = self.__map[key] = [last, root, key]
48 | return dict_setitem(self, key, value)
49 |
50 | def __delitem__(self, key, dict_delitem=dict.__delitem__):
51 | 'od.__delitem__(y) <==> del od[y]'
52 | # Deleting an existing item uses self.__map to find the link which gets
53 | # removed by updating the links in the predecessor and successor nodes.
54 | dict_delitem(self, key)
55 | link_prev, link_next, _ = self.__map.pop(key)
56 | link_prev[1] = link_next # update link_prev[NEXT]
57 | link_next[0] = link_prev # update link_next[PREV]
58 |
59 | def __iter__(self):
60 | 'od.__iter__() <==> iter(od)'
61 | # Traverse the linked list in order.
62 | root = self.__root
63 | curr = root[1] # start at the first node
64 | while curr is not root:
65 | yield curr[2] # yield the curr[KEY]
66 | curr = curr[1] # move to next node
67 |
68 | def __reversed__(self):
69 | 'od.__reversed__() <==> reversed(od)'
70 | # Traverse the linked list in reverse order.
71 | root = self.__root
72 | curr = root[0] # start at the last node
73 | while curr is not root:
74 | yield curr[2] # yield the curr[KEY]
75 | curr = curr[0] # move to previous node
76 |
77 | def clear(self):
78 | 'od.clear() -> None. Remove all items from od.'
79 | root = self.__root
80 | root[:] = [root, root, None]
81 | self.__map.clear()
82 | dict.clear(self)
83 |
84 | # -- the following methods do not depend on the internal structure --
85 |
86 | def keys(self):
87 | 'od.keys() -> list of keys in od'
88 | return list(self)
89 |
90 | def values(self):
91 | 'od.values() -> list of values in od'
92 | return [self[key] for key in self]
93 |
94 | def items(self):
95 | 'od.items() -> list of (key, value) pairs in od'
96 | return [(key, self[key]) for key in self]
97 |
98 | def iterkeys(self):
99 | 'od.iterkeys() -> an iterator over the keys in od'
100 | return iter(self)
101 |
102 | def itervalues(self):
103 | 'od.itervalues -> an iterator over the values in od'
104 | for k in self:
105 | yield self[k]
106 |
107 | def iteritems(self):
108 | 'od.iteritems -> an iterator over the (key, value) pairs in od'
109 | for k in self:
110 | yield (k, self[k])
111 |
112 | update = collections.MutableMapping.update
113 |
114 | __update = update # let subclasses override update without breaking __init__
115 |
116 | __marker = object()
117 |
118 | def pop(self, key, default=__marker):
119 | '''od.pop(k[,d]) -> v, remove specified key and return the corresponding
120 | value. If key is not found, d is returned if given, otherwise KeyError
121 | is raised.
122 |
123 | '''
124 | if key in self:
125 | result = self[key]
126 | del self[key]
127 | return result
128 | if default is self.__marker:
129 | raise KeyError(key)
130 | return default
131 |
132 | def setdefault(self, key, default=None):
133 | 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
134 | if key in self:
135 | return self[key]
136 | self[key] = default
137 | return default
138 |
139 | def popitem(self, last=True):
140 | '''od.popitem() -> (k, v), return and remove a (key, value) pair.
141 | Pairs are returned in LIFO order if last is true or FIFO order if false.
142 |
143 | '''
144 | if not self:
145 | raise KeyError('dictionary is empty')
146 | key = next(reversed(self) if last else iter(self))
147 | value = self.pop(key)
148 | return key, value
149 |
150 | def __repr__(self, _repr_running={}):
151 | 'od.__repr__() <==> repr(od)'
152 | call_key = id(self), _get_ident()
153 | if call_key in _repr_running:
154 | return '...'
155 | _repr_running[call_key] = 1
156 | try:
157 | if not self:
158 | return '%s()' % (self.__class__.__name__,)
159 | return '%s(%r)' % (self.__class__.__name__, self.items())
160 | finally:
161 | del _repr_running[call_key]
162 |
163 | def __reduce__(self):
164 | 'Return state information for pickling'
165 | items = [[k, self[k]] for k in self]
166 | inst_dict = vars(self).copy()
167 | for k in vars(OrderedDict()):
168 | inst_dict.pop(k, None)
169 | if inst_dict:
170 | return (self.__class__, (items,), inst_dict)
171 | return self.__class__, (items,)
172 |
173 | def copy(self):
174 | 'od.copy() -> a shallow copy of od'
175 | return self.__class__(self)
176 |
177 | @classmethod
178 | def fromkeys(cls, iterable, value=None):
179 | '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S.
180 | If not specified, the value defaults to None.
181 |
182 | '''
183 | self = cls()
184 | for key in iterable:
185 | self[key] = value
186 | return self
187 |
188 | def __eq__(self, other):
189 | '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
190 | while comparison to a regular mapping is order-insensitive.
191 |
192 | '''
193 | if isinstance(other, OrderedDict):
194 | return dict.__eq__(self, other) and all(_imap(_eq, self, other))
195 | return dict.__eq__(self, other)
196 |
197 | def __ne__(self, other):
198 | 'od.__ne__(y) <==> od!=y'
199 | return not self == other
200 |
201 | # -- the following methods support python 3.x style dictionary views --
202 |
203 | def viewkeys(self):
204 | "od.viewkeys() -> a set-like object providing a view on od's keys"
205 | return KeysView(self)
206 |
207 | def viewvalues(self):
208 | "od.viewvalues() -> an object providing a view on od's values"
209 | return ValuesView(self)
210 |
211 | def viewitems(self):
212 | "od.viewitems() -> a set-like object providing a view on od's items"
213 | return ItemsView(self)
214 |
215 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/crypto/table.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | #
3 | # Copyright 2015 clowwindy
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | from __future__ import absolute_import, division, print_function, \
18 | with_statement
19 |
20 | import string
21 | import struct
22 | import hashlib
23 |
24 |
25 | __all__ = ['ciphers']
26 |
27 | cached_tables = {}
28 |
29 | if hasattr(string, 'maketrans'):
30 | maketrans = string.maketrans
31 | translate = string.translate
32 | else:
33 | maketrans = bytes.maketrans
34 | translate = bytes.translate
35 |
36 |
37 | def get_table(key):
38 | m = hashlib.md5()
39 | m.update(key)
40 | s = m.digest()
41 | a, b = struct.unpack(' 1:
73 | stat_counter_dict = None
74 | else:
75 | stat_counter_dict = {}
76 | port_password = config['port_password']
77 | config_password = config.get('password', 'm')
78 | del config['port_password']
79 | for port, password_obfs in port_password.items():
80 | method = config["method"]
81 | protocol = config.get("protocol", 'origin')
82 | protocol_param = config.get("protocol_param", '')
83 | obfs = config.get("obfs", 'plain')
84 | obfs_param = config.get("obfs_param", '')
85 | bind = config.get("out_bind", '')
86 | bindv6 = config.get("out_bindv6", '')
87 | if type(password_obfs) == list:
88 | password = password_obfs[0]
89 | obfs = common.to_str(password_obfs[1])
90 | if len(password_obfs) > 2:
91 | protocol = common.to_str(password_obfs[2])
92 | elif type(password_obfs) == dict:
93 | password = password_obfs.get('password', config_password)
94 | method = common.to_str(password_obfs.get('method', method))
95 | protocol = common.to_str(password_obfs.get('protocol', protocol))
96 | protocol_param = common.to_str(password_obfs.get('protocol_param', protocol_param))
97 | obfs = common.to_str(password_obfs.get('obfs', obfs))
98 | obfs_param = common.to_str(password_obfs.get('obfs_param', obfs_param))
99 | bind = password_obfs.get('out_bind', bind)
100 | bindv6 = password_obfs.get('out_bindv6', bindv6)
101 | else:
102 | password = password_obfs
103 | a_config = config.copy()
104 | ipv6_ok = False
105 | logging.info("server start with protocol[%s] password [%s] method [%s] obfs [%s] obfs_param [%s]" %
106 | (protocol, password, method, obfs, obfs_param))
107 | if 'server_ipv6' in a_config:
108 | try:
109 | if len(a_config['server_ipv6']) > 2 and a_config['server_ipv6'][0] == "[" and a_config['server_ipv6'][-1] == "]":
110 | a_config['server_ipv6'] = a_config['server_ipv6'][1:-1]
111 | a_config['server_port'] = int(port)
112 | a_config['password'] = password
113 | a_config['method'] = method
114 | a_config['protocol'] = protocol
115 | a_config['protocol_param'] = protocol_param
116 | a_config['obfs'] = obfs
117 | a_config['obfs_param'] = obfs_param
118 | a_config['out_bind'] = bind
119 | a_config['out_bindv6'] = bindv6
120 | a_config['server'] = a_config['server_ipv6']
121 | logging.info("starting server at [%s]:%d" %
122 | (a_config['server'], int(port)))
123 | tcp_servers.append(tcprelay.TCPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict))
124 | udp_servers.append(udprelay.UDPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict))
125 | if a_config['server_ipv6'] == b"::":
126 | ipv6_ok = True
127 | except Exception as e:
128 | shell.print_exception(e)
129 |
130 | try:
131 | a_config = config.copy()
132 | a_config['server_port'] = int(port)
133 | a_config['password'] = password
134 | a_config['method'] = method
135 | a_config['protocol'] = protocol
136 | a_config['protocol_param'] = protocol_param
137 | a_config['obfs'] = obfs
138 | a_config['obfs_param'] = obfs_param
139 | a_config['out_bind'] = bind
140 | a_config['out_bindv6'] = bindv6
141 | logging.info("starting server at %s:%d" %
142 | (a_config['server'], int(port)))
143 | tcp_servers.append(tcprelay.TCPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict))
144 | udp_servers.append(udprelay.UDPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict))
145 | except Exception as e:
146 | if not ipv6_ok:
147 | shell.print_exception(e)
148 |
149 | def run_server():
150 | def child_handler(signum, _):
151 | logging.warn('received SIGQUIT, doing graceful shutting down..')
152 | list(map(lambda s: s.close(next_tick=True),
153 | tcp_servers + udp_servers))
154 | signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM),
155 | child_handler)
156 |
157 | def int_handler(signum, _):
158 | sys.exit(1)
159 | signal.signal(signal.SIGINT, int_handler)
160 |
161 | try:
162 | loop = eventloop.EventLoop()
163 | dns_resolver.add_to_loop(loop)
164 | list(map(lambda s: s.add_to_loop(loop), tcp_servers + udp_servers))
165 |
166 | daemon.set_user(config.get('user', None))
167 | loop.run()
168 | except Exception as e:
169 | shell.print_exception(e)
170 | sys.exit(1)
171 |
172 | if int(config['workers']) > 1:
173 | if os.name == 'posix':
174 | children = []
175 | is_child = False
176 | for i in range(0, int(config['workers'])):
177 | r = os.fork()
178 | if r == 0:
179 | logging.info('worker started')
180 | is_child = True
181 | run_server()
182 | break
183 | else:
184 | children.append(r)
185 | if not is_child:
186 | def handler(signum, _):
187 | for pid in children:
188 | try:
189 | os.kill(pid, signum)
190 | os.waitpid(pid, 0)
191 | except OSError: # child may already exited
192 | pass
193 | sys.exit()
194 | signal.signal(signal.SIGTERM, handler)
195 | signal.signal(signal.SIGQUIT, handler)
196 | signal.signal(signal.SIGINT, handler)
197 |
198 | # master
199 | for a_tcp_server in tcp_servers:
200 | a_tcp_server.close()
201 | for a_udp_server in udp_servers:
202 | a_udp_server.close()
203 | dns_resolver.close()
204 |
205 | for child in children:
206 | os.waitpid(child, 0)
207 | else:
208 | logging.warn('worker is only available on Unix/Linux')
209 | run_server()
210 | else:
211 | run_server()
212 |
213 |
214 | if __name__ == '__main__':
215 | main()
216 |
--------------------------------------------------------------------------------
/ssshare/donation.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | decline = '我不加微信,有事还是email联系吧:)'
3 | # info contains (number, message, name, reply)
4 | info = [
5 | (2, "感谢无私分享", "QQTelnet"),
6 | (0.1, "我要vpn15892106435v", "", "我也想要"),
7 | (0.01, "有高质量付费的节点吗", "", "没"),
8 | (1,),
9 | (0.4,),
10 | (2, "大神+V18606939505"),
11 | (1, "加微信18606939505", "", decline),
12 | (3,),
13 | (5, "再接再厉"),
14 | (0.01, "", "wx:aixwsm"),
15 | (0.01, "我想买高速稳定的ssr", "", "我也不知道哪里有卖"),
16 | (3,),
17 | (10,),
18 | (0.1,),
19 | (10, "临时用一下 非常感谢"),
20 | (10,),
21 | (1, "感谢分享"),
22 | (2,),
23 | (20, "感谢"),
24 | (10, "学生党祝好人一生平安"),
25 | (6.66,),
26 | (1, "谢谢分享"),
27 | (2,),
28 | (3,),
29 | (2,),
30 | (0.1, "如何联系你及购买", "", "我不提供收费服务,联系方式在主页面"),
31 | (1,),
32 | (5,),
33 | (1,),
34 | (5,),
35 | (5, "加个微信shanghai0412", "", decline),
36 | (5,),
37 | (20, "感谢提供SSR订阅", "Jeff"),
38 | (2,),
39 | (2,),
40 | (6.66, "谢谢分享!"),
41 | (10,),
42 | (1, "感谢"),
43 | (2,),
44 | (1,),
45 | (3, "感谢分享!!!"),
46 | (5,),
47 | (1, "过些日子在进行资助"),
48 | (10, "感谢您的付出,合十"),
49 | (1,),
50 | (0.1,),
51 | (0.1,),
52 | (6,),
53 | (2,),
54 | (5,),
55 | (0.1,),
56 | (5.2, "13870304931请宁+我", "", decline),
57 | (1, "谢谢"),
58 | (20,),
59 | (0.99,),
60 | (0.5,),
61 | (5,),
62 | (1,),
63 | (1, "资助"),
64 | (6.66, "感谢分享!"),
65 | (1.88, "多谢的分享"),
66 | (0.88,),
67 | (1,),
68 | (1, "不知道给多少...", "", "看心情就好,毕竟俺也不是靠这个维持生计:)"),
69 | (5, "谢大佬!一般用电脑连"),
70 | (3,),
71 | (2, "激励"),
72 | (10, "666,多谢分享"),
73 | (1,),
74 | (0.1, "Yhmaizi321haha加我", "", "还是用email沟通比较好"),
75 | (0.01, "请问有巴西节点么?", "", "这个要看提供的人了,我想没有"),
76 | (1, "想和你搞基", "", "不用了,我爱好女"),
77 | (1,),
78 | (5, "早餐"),
79 | (20,),
80 | (1, "不知道节点能坚持多久"),
81 | (1, "很ok!"),
82 | (1,),
83 | (1, "卢本伟没有开挂"),
84 | (0.01,),
85 | (10, "谢谢"),
86 | (1,),
87 | (5,),
88 | (1,),
89 | (5, "我成功了,谢谢。^_^"),
90 | (0.01,),
91 | (0.1,),
92 | (10,),
93 | (2, "感谢无私分享", "jasonx"),
94 | (10,),
95 | (2, "感谢无私奉献。我爱你"),
96 | (2, '', "花名酷狗"),
97 | (1, "感谢日本ip分享"),
98 | (1,),
99 | (5,),
100 | (10, "尊重劳动者,谢谢大佬"),
101 | (0.5,),
102 | (1,),
103 | (1,),
104 | (5, "小哥sth貌似失效了", '', "没失效,而是爬到的账号好用的比较少了"),
105 | (5,),
106 | (10, "谢谢你的共享"),
107 | (10,),
108 | (10,),
109 | (10, "FREE SS ID IS GOOD!!"),
110 | (0.9,),
111 | (6,),
112 | (1.6,),
113 | (1,),
114 | (5,),
115 | (1,),
116 | (1,),
117 | (0.01, "哥我弄不来教一下", '', "百度是个好东西"),
118 | (10, "谢谢节点"),
119 | (0.01,),
120 | (10, "继续加油呀"),
121 | (1, "感觉分享,!"),
122 | (10, "感谢无私分享"),
123 | (12.5,),
124 | (10, "加油!"),
125 | (10, "谢谢", "微信号21376089"),
126 | (10, "谢谢。限流吗兄弟", '', "这要看账号的提供方了,通常都限流"),
127 | (1,),
128 | (5, "感谢分享"),
129 | (1, "感谢提供,下次多赞助"),
130 | (0.1,),
131 | (5, "很好用,感恩感恩mua"),
132 | (10,),
133 | (1,),
134 | (5, "刚把爹!"),
135 | (10,),
136 | (5, "谢谢你"),
137 | (1,),
138 | (5,),
139 | (5,),
140 | (10, "大佬加油"),
141 | (1, "临时用,谢谢",),
142 | (1.66, "又见Twitter",),
143 | (2,),
144 | (10, "谢谢地址加油 emoji:肱二头肌",),
145 | (5, "谢啦朋友,小小心意",),
146 | (1,),
147 | (5,),
148 | (1,),
149 | (5,),
150 | (0.1,),
151 | (5.2, "感谢!!!",),
152 | (0.73,),
153 | (6,),
154 | (3,),
155 | (1, "谢谢",),
156 | (5,),
157 | (0.52,),
158 | (1.88, "谢谢分享",),
159 | (10, "请大佬喝肥宅快乐水",),
160 | (10,),
161 | (5,),
162 | (8, "感谢,希望越来越好", '', "可能要让你失望了,现在能找到的账号越来越少了",),
163 | (6.66,),
164 | (0.1,),
165 | (5, "经济的ss云服务器?", '', "vultr, bandwagon, linode, digitalocean, aws之类的,老生常谈的一般就这些吧",),
166 | (2,),
167 | (1, "感谢ssr",),
168 | (1,),
169 | (3, "一瓶冰阔落",),
170 | (2.33,),
171 | (1,),
172 | (1.99, "感谢分享。", "zephyr",),
173 | (3,),
174 | (1,),
175 | (5, "有点少,别介意啊哥",),
176 | (10,),
177 | (8.86, "大灰狼祝新年快乐 emoji:烟花",),
178 | (0.99,),
179 | (12.5,),
180 | (5,),
181 | (5, "感谢 emoji:合掌",),
182 | (4, "连上了,小小的感谢",),
183 | (0.01, "渣渣",),
184 | (1,),
185 | (5, "棒极了",),
186 | (5, "感谢科学",),
187 | (0.1, "请加我为信号", "axxx0257", "我主页上有email,聊email更安全:)",),
188 | (10,),
189 | (15,),
190 | (10, "谢谢分享",),
191 | (1,),
192 | (50, "感谢",),
193 | (0.01, "加油哦",),
194 | (0.01,),
195 | (0.01,),
196 | (10,),
197 | (50, "你好,可以加下微信吗", "", "主页上有我的email,聊email更安全:)",),
198 | (0.01,),
199 | (1,),
200 | (0.1,),
201 | (1, "谢谢,加油",),
202 | (0.1,),
203 | (5, "竟然能用,就很舒服",),
204 | (6, "最棒的梯子!",),
205 | (20,),
206 | (10,),
207 | (0.01,),
208 | (0.1, "你好 能加个朋友吗", '', "联系我可以向主页上的邮箱发邮件:)"),
209 | (10, "想问些问题不浪费时间"),
210 | (1,),
211 | (0.5,),
212 | (5,),
213 | (10, "谢谢"),
214 | (5, "谢谢你的账号", '', "账号全部来源于互联网大家的无私分享,请支持账号来源:)"),
215 | (10, "谢大佬!请喝可乐"),
216 | (5,),
217 | (10,),
218 | (20,),
219 | (5,),
220 | (20,),
221 | (6.66,),
222 | (10,),
223 | (0.01,),
224 | (1,),
225 | (2,),
226 | (0.1,),
227 | (5,),
228 | (5, "还差一个y", "willyboyshy"),
229 | (5, "willyboysh", "willyboyshy"),
230 | (10, "可以的话加我微信", "willyboyshy"),
231 | (10, "一边让我卖,打持成本", "willyboyshy"),
232 | (10, "说能不能一边公益,", "willyboyshy"),
233 | (10, "的,无奈人数太少,想", "willyboyshy"),
234 | (10, "一起合作,我是开机场", "willyboyshy"),
235 | (10, "你好,我想请问能不能", "willyboyshy", "抱歉,我不做商业活动"),
236 | (0.12,),
237 | (2.33,),
238 | (15, "支持"),
239 | (1,),
240 | (2,),
241 | (1,),
242 | (2.33, "看起来很厉害"),
243 | (0.01, "我要80端口哪里买!"),
244 | (5,),
245 | (0.01,),
246 | (5, "感谢你的订阅源"),
247 | (1, "谢谢大佬"),
248 | (10, "感谢大佬的付出"),
249 | (6.66, "感谢老哥的ssr"),
250 | (5,),
251 | (2,),
252 | (5, "感谢分享"),
253 | (1,),
254 | (1,),
255 | (10, "谢谢你自由网络的贡献"),
256 | (10, "多谢分享!加油!"),
257 | (5,),
258 | (1, "谢谢分享"),
259 | (2,),
260 | (10, "感谢ss账号"),
261 | (1,),
262 | (1,),
263 | (6.66, "谢谢", "微信15901219117"),
264 | (8.88, "感谢大佬"),
265 | (5,),
266 | (10, "还是学生,一点小心意"),
267 | (1,),
268 | (2.33, "大佬tg多少?"),
269 | (1,),
270 | (66.66,),
271 | (0.1,),
272 | (5, "谢谢"),
273 | (5,),
274 | (9, "今晚加个鸡腿吧!"),
275 | (0.5,),
276 | (6.66,),
277 | (4, "感谢", "淡定"),
278 | (10,),
279 | (10,),
280 | (1,),
281 | (1, "", "sz6101238"),
282 | (6.66,),
283 | (2,),
284 | (2.33,),
285 | (1,),
286 | (3, "成功了,感谢!"),
287 | (3,),
288 | (1, "感谢您的梯子~"),
289 | (1, "感谢分享"),
290 | (1, "加油继续做下去", "花郎"),
291 | (20, "", 'llyy7'),
292 | (5,),
293 | (1, "Thank you"),
294 | (10,),
295 | (8.88,),
296 | (5,),
297 | (10, '感谢分享,'),
298 | (3,),
299 | (3.14, '买瓶可乐吧,兄弟'),
300 | (1,),
301 | (1.11, 'saner'),
302 | (10, 'HOLD ON!'),
303 | (5.2, '谢谢分享'),
304 | (5, '大佬喝冰阔落'),
305 | (20, '支持一下 emoji:smile'),
306 | (6.66, '谢谢'),
307 | (10,),
308 | (1, '感谢大佬!'),
309 | (10,),
310 | (10, '支持'),
311 | (1,),
312 | (2, '万分感谢'),
313 | (8.88, '再接再励。'),
314 | (10, '谢谢你的努力'),
315 | (10, '很好用,点赞!'),
316 | (2.33, '牛批'),
317 | (1,),
318 | (1, '等发工资再捐'),
319 | (5, '老铁 奉献没毛病'),
320 | (2, '加油兄弟'),
321 | (1, '好用,点赞!继续保持'),
322 | (8.8, '聊表心意,钱不多。'),
323 | (10,),
324 | (10, '谢谢'),
325 | (10, '谢谢分享'),
326 | (10, '资助你大兄弟加油'),
327 | (1, '加油哦!Ծ̮Ծ'),
328 | (10,),
329 | (5, '779595014'),
330 | (3, 'Respect!'),
331 | (0.01,),
332 | (5, '支持技术!谢谢技术!'),
333 | (5, '感谢分享'),
334 | (20, '感谢分享,自由无价!'),
335 | (20, '请再接再厉,坚持完善'),
336 | (2,),
337 | (10, '谢谢分享!'),
338 | (0.88,),
339 | (3, '感谢你免费梯子项目'),
340 | (8.88,),
341 | (5,),
342 | (5,),
343 | (10.01, '伟大,感谢与支持!'),
344 | (1, '用不了了(emoji哭)不过还是', '', '具体是哪方面用不了了?欢迎到我的 GitHub Issue 页面提供反馈;
此外近期有计划抽空对网站升级,把不能用的账号过滤掉:)'),
345 | (10, ''),
346 | (10, '感谢分享~加油'),
347 | (8, '喝瓶奶茶'),
348 | (5, '老大,请你吃个可乐。'),
349 | (10, '感谢您的ss项目'),
350 | (2.33, '谢谢'),
351 | (1, ''),
352 | (10, '感谢分享免费梯子'),
353 | (2, ''),
354 | (50, '', 'hsu_bicheng'),
355 | ]
356 |
357 |
358 | def parse(data):
359 | if len(data) == 1:
360 | number, message = data[0], ''
361 | else:
362 | number, message = data[:2]
363 | if message:
364 | message = ',并留言:”{}“'.format(message)
365 | if len(data) >= 3:
366 | name = '' + data[2] + ''
367 | else:
368 | name = '某位没有留下名字的'
369 |
370 | if len(data) >= 4:
371 | comment = '
to 这位朋友:{}'.format(data[3])
372 | else:
373 | comment = ''
374 |
375 | return_data = '{name}朋友捐献了{number}元{message}:){comment}
'.format(
376 | name=name,
377 | number=number,
378 | message=message,
379 | comment=comment)
380 | return return_data
381 |
382 |
383 | sum_people = len(info)
384 | sum_money = '{:.2f}'.format(sum(list(zip(*info))[0]))
385 | data = '\n'.join(map(parse, info))
386 |
387 |
388 | if __name__ == '__main__':
389 | print("sum_people: ", sum_people)
390 | print("sum_money: ", sum_money)
391 |
--------------------------------------------------------------------------------
/ssshare/js/clipboard.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * clipboard.js v1.7.1
3 | * https://zenorocha.github.io/clipboard.js
4 | *
5 | * Licensed MIT © Zeno Rocha
6 | */
7 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,o){function i(a,c){if(!n[a]){if(!e[a]){var l="function"==typeof require&&require;if(!c&&l)return l(a,!0);if(r)return r(a,!0);var s=new Error("Cannot find module '"+a+"'");throw s.code="MODULE_NOT_FOUND",s}var u=n[a]={exports:{}};e[a][0].call(u.exports,function(t){var n=e[a][1][t];return i(n||t)},u,u.exports,t,e,n,o)}return n[a].exports}for(var r="function"==typeof require&&require,a=0;a0&&void 0!==arguments[0]?arguments[0]:{};this.action=e.action,this.container=e.container,this.emitter=e.emitter,this.target=e.target,this.text=e.text,this.trigger=e.trigger,this.selectedText=""}},{key:"initSelection",value:function t(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function t(){var e=this,n="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return e.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[n?"right":"left"]="-9999px";var o=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=o+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,i.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function t(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function t(){this.selectedText=(0,i.default)(this.target),this.copyText()}},{key:"copyText",value:function t(){var e=void 0;try{e=document.execCommand(this.action)}catch(t){e=!1}this.handleResult(e)}},{key:"handleResult",value:function t(e){this.emitter.emit(e?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function t(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function t(){this.removeFake()}},{key:"action",set:function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=e,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function t(){return this._action}},{key:"target",set:function t(e){if(void 0!==e){if(!e||"object"!==(void 0===e?"undefined":r(e))||1!==e.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&e.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(e.hasAttribute("readonly")||e.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=e}},get:function t(){return this._target}}]),t}();t.exports=c})},{select:5}],8:[function(e,n,o){!function(i,r){if("function"==typeof t&&t.amd)t(["module","./clipboard-action","tiny-emitter","good-listener"],r);else if(void 0!==o)r(n,e("./clipboard-action"),e("tiny-emitter"),e("good-listener"));else{var a={exports:{}};r(a,i.clipboardAction,i.tinyEmitter,i.goodListener),i.clipboard=a.exports}}(this,function(t,e,n,o){"use strict";function i(t){return t&&t.__esModule?t:{default:t}}function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function a(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function c(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function l(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}var s=i(e),u=i(n),f=i(o),d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},h=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof e.action?e.action:this.defaultAction,this.target="function"==typeof e.target?e.target:this.defaultTarget,this.text="function"==typeof e.text?e.text:this.defaultText,this.container="object"===d(e.container)?e.container:document.body}},{key:"listenClick",value:function t(e){var n=this;this.listener=(0,f.default)(e,"click",function(t){return n.onClick(t)})}},{key:"onClick",value:function t(e){var n=e.delegateTarget||e.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new s.default({action:this.action(n),target:this.target(n),text:this.text(n),container:this.container,trigger:n,emitter:this})}},{key:"defaultAction",value:function t(e){return l("action",e)}},{key:"defaultTarget",value:function t(e){var n=l("target",e);if(n)return document.querySelector(n)}},{key:"defaultText",value:function t(e){return l("text",e)}},{key:"destroy",value:function t(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],n="string"==typeof e?[e]:e,o=!!document.queryCommandSupported;return n.forEach(function(t){o=o&&!!document.queryCommandSupported(t)}),o}}]),e}(u.default);t.exports=p})},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)});
--------------------------------------------------------------------------------
/ssshare/ss/crawler.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | {
5 | "server": server['server'],
6 | "server_ipv6": "::",
7 | "server_port": int(server['server_port']),
8 | "local_address": "127.0.0.1",
9 | "local_port": 1080,
10 | "password": server['password'],
11 | "timeout": 300,
12 | "udp_timeout": 60,
13 | "method": method,
14 | "protocol": ssr_protocol,
15 | "protocol_param": "",
16 | "obfs": obfs,
17 | "obfs_param": "",
18 | "fast_open": False,
19 | "workers": 1,
20 | "group": "ss.pythonic.life"
21 | }
22 | """
23 | import json
24 | import logging
25 | import time
26 | from ast import literal_eval
27 |
28 | import cfscrape
29 | import js2py
30 | import regex as re
31 | import requests
32 | from bs4 import BeautifulSoup
33 | from ssshare.ss.parse import decode, gen_uri, parse, scanNetQR
34 | from ssshare.ss.ssr_check import validate
35 | from ssshare.ss.utils import robots_get
36 | from ssshare.config import MODE
37 |
38 | soft_user_agent = {'User-Agent': 'ShadowSocksShare/web/crawler'}
39 | brow_user_agent = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
40 |
41 | if MODE == 'strict':
42 | print('strict mode on')
43 | user_agent = soft_user_agent
44 | get_url = robots_get
45 | else:
46 | user_agent = brow_user_agent
47 | get_url = requests.get
48 |
49 |
50 | def request_url(url, headers=None, name=''):
51 | print('req', url)
52 |
53 | data = set()
54 | servers = list()
55 | try:
56 | response = get_url(url, headers=headers, verify=False).text
57 | data.update(map(lambda x: re.sub('\s', '', x), re.findall('ssr?://[a-zA-Z0-9_]+=*', response)))
58 | soup = BeautifulSoup(response, 'html.parser')
59 | title = soup.find('title')
60 | if title:
61 | title = title.text
62 | else:
63 | title = name
64 |
65 | info = {'message': '', 'url': url, 'name': str(title)}
66 | for i, server in enumerate(data):
67 | try:
68 | servers.append(parse(server, ' '.join([title, name, str(i)])))
69 | except Exception as e:
70 | logging.exception(e, stack_info=False)
71 | print('URL:', url, 'SERVER', server)
72 | except Exception as e:
73 | print(url)
74 | logging.exception(e, stack_info=False)
75 | return [], {'message': str(e), 'url': '', 'name': ''}
76 | return servers, info
77 |
78 |
79 | # def request_subscription(url, headers=user_agent, name='ssr 订阅源'):
80 | def request_subscription(url, headers=user_agent, name='ssr 订阅源'):
81 | print('req', url)
82 | data = get_url(url, headers=headers).text
83 | ssr_links = [i for i in decode(data).split('\n') if len(i) > 3]
84 | info = {
85 | 'message': '来自订阅源 {}'.format(url),
86 | 'url': url,
87 | 'name': str(name),
88 | }
89 | servers = list()
90 | for i, link in enumerate(ssr_links):
91 | try:
92 | servers.append(parse(link, ' '.join([name, str(i)])))
93 | except Exception as e:
94 | logging.exception(e, stack_info=False)
95 | print('URL:', url, 'SERVER', link)
96 | return servers, info
97 |
98 |
99 | # def crawl_sstool(url='https://www.ssrtool.com/', headers=user_agent):
100 | def crawl_sstool(url='https://www.ssrtool.com/', headers=user_agent):
101 | api_url = [url + i for i in [
102 | 'tool/api/free_ssr?page=1&limit=10',
103 | 'tool/api/free_ssr?page=2&limit=10',
104 | 'tool/api/free_ssr?page=3&limit=10',
105 | 'tool/api/free_ssr?page=4&limit=10',
106 | 'tool/api/free_ssr?page=5&limit=10',
107 | 'tool/api/free_ssr?page=6&limit=10',
108 | 'tool/api/free_ssr?page=7&limit=10',
109 | 'tool/api/free_ssr?page=8&limit=10',
110 | 'tool/api/free_ssr?page=9&limit=10',
111 | 'tool/api/free_ssr?page=10&limit=10',
112 |
113 | 'tool/api/share_ssr?page=1&limit=10',
114 | 'tool/api/share_ssr?page=2&limit=10',
115 | 'tool/api/share_ssr?page=3&limit=10',
116 | 'tool/api/share_ssr?page=4&limit=10',
117 | 'tool/api/share_ssr?page=5&limit=10',
118 | 'tool/api/share_ssr?page=6&limit=10',
119 | 'tool/api/share_ssr?page=7&limit=10',
120 | 'tool/api/share_ssr?page=8&limit=10',
121 | 'tool/api/share_ssr?page=9&limit=10',
122 | 'tool/api/share_ssr?page=10&limit=10',
123 | ]]
124 | print('req sstool')
125 | info = {
126 | 'message': 'GayHub订阅(3天自动更新一次):\nhttps://raw.githubusercontent.com/AmazingDM/sub/master/ssrshare.com',
127 | 'name': 'SSCAP/SSTAP 小工具/SSR/SS/V2Ray/Vmess/Socks5免费账号',
128 | 'url': url,
129 | }
130 | data = list()
131 | try:
132 | for url in api_url:
133 | time.sleep(2)
134 | # respond = get_url(url, headers=user_agent)
135 | respond = get_url(url, headers=user_agent)
136 | if respond.status_code == 200:
137 | json_data = respond.json()
138 | data.extend(json_data.get('data', []))
139 | for i in data:
140 | if 'protocol' in i:
141 | i['ssr_protocol'] = i['protocol']
142 | if 'protocolparam' in i:
143 | i['protoparam'] = i['protocolparam']
144 | else:
145 | print(i)
146 | servers = data
147 |
148 | except Exception as e:
149 | logging.exception(e, stack_info=True)
150 | print('-' * 30)
151 | return servers, info
152 |
153 |
154 | # def crawl_free_ss_site(url='https://free-ss.site/', headers=user_agent):
155 | def crawl_free_ss_site(url='https://free-ss.site/', headers=user_agent):
156 | print('req free_ss_site/...')
157 | info = {'message': '', 'name': '免费上网账号', 'url': 'https://free-ss.site/'}
158 | try:
159 | # user_agent = headers
160 | user_agent = headers
161 |
162 | sess = cfscrape.create_scraper()
163 |
164 | encc_url = url + 'ajax/libs/encc/0.0.0/encc.min.js'
165 | crypto_url = url + 'ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js'
166 |
167 | headers = {'Referer': url, 'Origin': url}
168 | # headers.update(user_agent)
169 | headers.update(user_agent)
170 |
171 | # html = sess.get(url, headers=user_agent).text
172 | html = sess.get(url, headers=user_agent).text
173 | print('html')
174 | encc_js = sess.get(encc_url, headers=headers).text
175 | print('encc')
176 | crypto_js = sess.get(crypto_url, headers=headers).text
177 | print('crypto')
178 |
179 | soup = BeautifulSoup(html, 'html.parser')
180 | script = next(filter(lambda x: 'src' not in x.attrs, soup.findAll('script'))).contents[0]
181 |
182 | def get_value(char):
183 | value_exp = re.findall(r'(?<=var)\s+{char}\s*=\s*[^;]+(?=;)'.format(char=char), script)[1]
184 | return literal_eval(value_exp.split('=')[1])
185 |
186 | value_dict = {
187 | 'a': get_value('a'),
188 | 'b': get_value('b'),
189 | 'c': get_value('c'),
190 | }
191 |
192 | value_dict['c'] = js2py.eval_js(encc_js + 'encc("{c}");'.format(c=value_dict['c']))
193 | replace_char = re.findall(r'(?<=function\()\w(?=\)\{\s*\$.post\()', script, flags=re.MULTILINE)[0]
194 | img = re.findall(r"(?<=decodeImage\(')data:image[^']+(?=')", script)[0]
195 | value_dict[replace_char] = scanNetQR(img)
196 |
197 | d = sess.post(
198 | url + "data.php",
199 | headers=headers,
200 | data={
201 | 'a': value_dict['a'],
202 | 'b': value_dict['b'],
203 | 'c': value_dict['c']
204 | })
205 | print(d.status_code)
206 | d = d.text
207 | print(len(d))
208 | assert len(d) > 0, 'request for encrypted data failed'
209 |
210 | variables = ';var a = "{a}";var d = "{d}";var b = "{b}"'.format(
211 | a=value_dict['a'],
212 | b=value_dict['b'],
213 | d=d
214 | )
215 | xy = ';var x=CryptoJS.enc.Latin1.parse(a);var y=CryptoJS.enc.Latin1.parse(b);'
216 | ev = js2py.eval_js(re.findall(r'(?<=eval)\(function.*', script)[1]) + 'dec.toString(CryptoJS.enc.Utf8);'
217 |
218 | json_data = js2py.eval_js(crypto_js + variables + xy + ev)
219 | try:
220 | data = json.loads(json_data)
221 | data = data['data']
222 | except Exception as e:
223 | print(e)
224 | print(json_data)
225 |
226 | servers = [{
227 | 'remarks': x[6],
228 | 'server': x[1],
229 | 'server_port': x[2],
230 | 'password': x[3],
231 | 'method': x[4],
232 | } for x in data]
233 | except Exception as e:
234 | logging.exception(e, stack_info=True)
235 | print(value_dict)
236 | print(d)
237 | print('-' * 30)
238 | return servers, info
239 |
240 |
241 | def main(debug=list()):
242 |
243 | result = list()
244 | # Specified functions for requesting servers
245 | websites = [globals()[i] for i in filter(lambda x: x.startswith('crawl_'), globals())]
246 | from ssshare.config import url, subscriptions
247 |
248 | websites.extend([(i, None) for i in url])
249 |
250 | for website in websites:
251 | try:
252 | if type(website) is tuple:
253 | data, info = request_url(*website)
254 | else:
255 | data, info = website()
256 | result.append({'data': gen_uri(data), 'info': info})
257 | except Exception as e:
258 | logging.exception(e, stack_info=False)
259 | print('Error in', website, type(website))
260 |
261 | for subscription in subscriptions:
262 | try:
263 | data, info = request_subscription(subscription)
264 | result.append({'data': gen_uri(data), 'info': info})
265 | except Exception as e:
266 | logging.exception(e, stack_info=False)
267 | print('Error in', subscription, type(subscription))
268 |
269 | # remove duplicates
270 | uniq_result = list()
271 | keys = set()
272 | for server in result:
273 | server_data = server.get('data', [])
274 | uniqed_server_data = list()
275 | for ssr_data in server_data:
276 | key = '{}:{}'.format(ssr_data.get('server', ''), ssr_data.get('server_port', ''))
277 | if key not in keys:
278 | keys.add(key)
279 | uniqed_server_data.append(ssr_data)
280 | uniq_result.append({'data': uniqed_server_data, 'info': server['info']})
281 |
282 | # check ssr configs
283 | if 'no_validate' in debug:
284 | validated_servers = uniq_result
285 | else:
286 | validated_servers = validate(uniq_result)
287 | # remove useless data
288 | servers = list(filter(lambda x: len(x['data']) > 0, validated_servers))
289 | print('-' * 10, '数据获取完毕', '-' * 10)
290 | return servers
291 |
--------------------------------------------------------------------------------
/ssshare/shadowsocks/manager.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2015 clowwindy
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | # not use this file except in compliance with the License. You may obtain
8 | # a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 | # License for the specific language governing permissions and limitations
16 | # under the License.
17 |
18 | from __future__ import absolute_import, division, print_function, \
19 | with_statement
20 |
21 | import errno
22 | import traceback
23 | import socket
24 | import logging
25 | import json
26 | import collections
27 |
28 | from ssshare.shadowsocks import common, eventloop, tcprelay, udprelay, asyncdns, shell
29 |
30 |
31 | BUF_SIZE = 1506
32 | STAT_SEND_LIMIT = 50
33 |
34 |
35 | class Manager(object):
36 |
37 | def __init__(self, config):
38 | self._config = config
39 | self._relays = {} # (tcprelay, udprelay)
40 | self._loop = eventloop.EventLoop()
41 | self._dns_resolver = asyncdns.DNSResolver()
42 | self._dns_resolver.add_to_loop(self._loop)
43 |
44 | self._statistics = collections.defaultdict(int)
45 | self._control_client_addr = None
46 | try:
47 | manager_address = common.to_str(config['manager_address'])
48 | if ':' in manager_address:
49 | addr = manager_address.rsplit(':', 1)
50 | addr = addr[0], int(addr[1])
51 | addrs = socket.getaddrinfo(addr[0], addr[1])
52 | if addrs:
53 | family = addrs[0][0]
54 | else:
55 | logging.error('invalid address: %s', manager_address)
56 | exit(1)
57 | else:
58 | addr = manager_address
59 | family = socket.AF_UNIX
60 | self._control_socket = socket.socket(family,
61 | socket.SOCK_DGRAM)
62 | self._control_socket.bind(addr)
63 | self._control_socket.setblocking(False)
64 | except (OSError, IOError) as e:
65 | logging.error(e)
66 | logging.error('can not bind to manager address')
67 | exit(1)
68 | self._loop.add(self._control_socket,
69 | eventloop.POLL_IN, self)
70 | self._loop.add_periodic(self.handle_periodic)
71 |
72 | port_password = config['port_password']
73 | del config['port_password']
74 | for port, password in port_password.items():
75 | a_config = config.copy()
76 | a_config['server_port'] = int(port)
77 | a_config['password'] = password
78 | self.add_port(a_config)
79 |
80 | def add_port(self, config):
81 | port = int(config['server_port'])
82 | servers = self._relays.get(port, None)
83 | if servers:
84 | logging.error("server already exists at %s:%d" % (config['server'],
85 | port))
86 | return
87 | logging.info("adding server at %s:%d" % (config['server'], port))
88 | t = tcprelay.TCPRelay(config, self._dns_resolver, False,
89 | stat_callback=self.stat_callback)
90 | u = udprelay.UDPRelay(config, self._dns_resolver, False,
91 | stat_callback=self.stat_callback)
92 | t.add_to_loop(self._loop)
93 | u.add_to_loop(self._loop)
94 | self._relays[port] = (t, u)
95 |
96 | def remove_port(self, config):
97 | port = int(config['server_port'])
98 | servers = self._relays.get(port, None)
99 | if servers:
100 | logging.info("removing server at %s:%d" % (config['server'], port))
101 | t, u = servers
102 | t.close(next_tick=False)
103 | u.close(next_tick=False)
104 | del self._relays[port]
105 | else:
106 | logging.error("server not exist at %s:%d" % (config['server'],
107 | port))
108 |
109 | def handle_event(self, sock, fd, event):
110 | if sock == self._control_socket and event == eventloop.POLL_IN:
111 | data, self._control_client_addr = sock.recvfrom(BUF_SIZE)
112 | parsed = self._parse_command(data)
113 | if parsed:
114 | command, config = parsed
115 | a_config = self._config.copy()
116 | if config:
117 | # let the command override the configuration file
118 | a_config.update(config)
119 | if 'server_port' not in a_config:
120 | logging.error('can not find server_port in config')
121 | else:
122 | if command == 'add':
123 | self.add_port(a_config)
124 | self._send_control_data(b'ok')
125 | elif command == 'remove':
126 | self.remove_port(a_config)
127 | self._send_control_data(b'ok')
128 | elif command == 'ping':
129 | self._send_control_data(b'pong')
130 | else:
131 | logging.error('unknown command %s', command)
132 |
133 | def _parse_command(self, data):
134 | # commands:
135 | # add: {"server_port": 8000, "password": "foobar"}
136 | # remove: {"server_port": 8000"}
137 | data = common.to_str(data)
138 | parts = data.split(':', 1)
139 | if len(parts) < 2:
140 | return data, None
141 | command, config_json = parts
142 | try:
143 | config = shell.parse_json_in_str(config_json)
144 | return command, config
145 | except Exception as e:
146 | logging.error(e)
147 | return None
148 |
149 | def stat_callback(self, port, data_len):
150 | self._statistics[port] += data_len
151 |
152 | def handle_periodic(self):
153 | r = {}
154 | i = 0
155 |
156 | def send_data(data_dict):
157 | if data_dict:
158 | # use compact JSON format (without space)
159 | data = common.to_bytes(json.dumps(data_dict,
160 | separators=(',', ':')))
161 | self._send_control_data(b'stat: ' + data)
162 |
163 | for k, v in self._statistics.items():
164 | r[k] = v
165 | i += 1
166 | # split the data into segments that fit in UDP packets
167 | if i >= STAT_SEND_LIMIT:
168 | send_data(r)
169 | r.clear()
170 | i = 0
171 | if len(r) > 0 :
172 | send_data(r)
173 | self._statistics.clear()
174 |
175 | def _send_control_data(self, data):
176 | if self._control_client_addr:
177 | try:
178 | self._control_socket.sendto(data, self._control_client_addr)
179 | except (socket.error, OSError, IOError) as e:
180 | error_no = eventloop.errno_from_exception(e)
181 | if error_no in (errno.EAGAIN, errno.EINPROGRESS,
182 | errno.EWOULDBLOCK):
183 | return
184 | else:
185 | shell.print_exception(e)
186 | if self._config['verbose']:
187 | traceback.print_exc()
188 |
189 | def run(self):
190 | self._loop.run()
191 |
192 |
193 | def run(config):
194 | manager = Manager(config).run()
195 | return manager._control_socket
196 |
197 |
198 | def test():
199 | import time
200 | import threading
201 | import struct
202 | from ssshare.shadowsocks import encrypt
203 |
204 | logging.basicConfig(level=5,
205 | format='%(asctime)s %(levelname)-8s %(message)s',
206 | datefmt='%Y-%m-%d %H:%M:%S')
207 | enc = []
208 | eventloop.TIMEOUT_PRECISION = 1
209 |
210 | def run_server():
211 | config = shell.get_config(True)
212 | config = config.copy()
213 | a_config = {
214 | 'server': '127.0.0.1',
215 | 'local_port': 1081,
216 | 'port_password': {
217 | '8381': 'foobar1',
218 | '8382': 'foobar2'
219 | },
220 | 'method': 'aes-256-cfb',
221 | 'manager_address': '127.0.0.1:6001',
222 | 'timeout': 60,
223 | 'fast_open': False,
224 | 'verbose': 2
225 | }
226 | config.update(a_config)
227 | manager = Manager(config)
228 | enc.append(manager)
229 | manager.run()
230 |
231 | t = threading.Thread(target=run_server)
232 | t.start()
233 | time.sleep(1)
234 | manager = enc[0]
235 | cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
236 | cli.connect(('127.0.0.1', 6001))
237 |
238 | # test add and remove
239 | time.sleep(1)
240 | cli.send(b'add: {"server_port":7001, "password":"asdfadsfasdf"}')
241 | time.sleep(1)
242 | assert 7001 in manager._relays
243 | data, addr = cli.recvfrom(1506)
244 | assert b'ok' in data
245 |
246 | cli.send(b'remove: {"server_port":8381}')
247 | time.sleep(1)
248 | assert 8381 not in manager._relays
249 | data, addr = cli.recvfrom(1506)
250 | assert b'ok' in data
251 | logging.info('add and remove test passed')
252 |
253 | # test statistics for TCP
254 | header = common.pack_addr(b'google.com') + struct.pack('>H', 80)
255 | data = encrypt.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', 1,
256 | header + b'GET /\r\n\r\n')
257 | tcp_cli = socket.socket()
258 | tcp_cli.connect(('127.0.0.1', 7001))
259 | tcp_cli.send(data)
260 | tcp_cli.recv(4096)
261 | tcp_cli.close()
262 |
263 | data, addr = cli.recvfrom(1506)
264 | data = common.to_str(data)
265 | assert data.startswith('stat: ')
266 | data = data.split('stat:')[1]
267 | stats = shell.parse_json_in_str(data)
268 | assert '7001' in stats
269 | logging.info('TCP statistics test passed')
270 |
271 | # test statistics for UDP
272 | header = common.pack_addr(b'127.0.0.1') + struct.pack('>H', 80)
273 | data = encrypt.encrypt_all(b'foobar2', 'aes-256-cfb', 1,
274 | header + b'test')
275 | udp_cli = socket.socket(type=socket.SOCK_DGRAM)
276 | udp_cli.sendto(data, ('127.0.0.1', 8382))
277 | tcp_cli.close()
278 |
279 | data, addr = cli.recvfrom(1506)
280 | data = common.to_str(data)
281 | assert data.startswith('stat: ')
282 | data = data.split('stat:')[1]
283 | stats = json.loads(data)
284 | assert '8382' in stats
285 | logging.info('UDP statistics test passed')
286 |
287 | manager._loop.stop()
288 | t.join()
289 |
290 |
291 | if __name__ == '__main__':
292 | test()
293 |
--------------------------------------------------------------------------------