├── 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 |
9 | 说明 10 |

微信现在看不到付款人信息了,所以捐款的时候想要留下名字就要在捐款信息里面附上-_-

11 |

谢谢大家支持,大家的捐款现在主要用于交域名费,等到攒到一定数额会考虑用捐款搭梯子分享给大家:)

12 |

由于我也没有搭梯子的经验,会考虑买很多不同家的VPS给大家测试(等我有空再说吧...)

13 |
14 |

共计{{sum_people}}位朋友捐款,共计{{sum_money}}元:)

15 |
16 |
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 |
19 | 20 | ShadowSocks服务器信息 21 | {% for server in servers %} 22 |

23 | 请支持免费ss账号的提供者:{{server['info']['name']}}
24 | 服务器通知:
{{server['info']['message']}}
25 |
26 |

    27 | {%set outer_loop = loop%} 28 | {% for s in server['data'] %} 29 |
  1. 30 | {{s['remarks']}} - {{s['status']}}ms 31 |
  2. 32 | {% endfor %} 33 |
34 |

35 | {% endfor %} 36 | 全部SSR账号订阅地址:链接
37 | 全部JSON格式账号的订阅地址(每次随机从库中抽取一条json返回):链接 38 |
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 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 54 |
平台链接备注
LinuxLinux 16 | 客户端一键安装配置使用脚本命令行界面, Mac 也能用,使用方法见注释
Terminaltsocks安装使用教程
WindowsSSR C# 版Windows 推荐使用
Android Android 客户端 35 |
macOSShadowsocksX-NG 42 | Mac 也能用 Linux 版的
iOS只有中国 App Store 账号, 美区 App Store:potatso-lite 只有中国区 App Store的话会麻烦一些
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 | 73 | 74 | 75 | 76 | 77 | 78 | Fork me on GitHub 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {%- if ssr_protocol != 'None' -%} 53 | 54 | 55 | 56 | 57 | {% endif %} 58 | {%- if obfs != 'None' -%} 59 | 60 | 61 | 62 | 63 | {% endif %} 64 | {%- if protoparam != 'None' -%} 65 | 66 | 67 | 68 | 69 | {% endif %} 70 | {%- if obfsparam != 'None' -%} 71 | 72 | 73 | 74 | 75 | {% endif %} 76 | 77 | 78 | 79 | 80 |
服务器{{server}}
端口{{server_port}}
密码{{password}}
加密方法{{method}}
SSR协议{{ssr_protocol}}
混淆方式{{obfs}}
协议参数{{protoparam}}
混淆参数{{obfsparam}}
连接状态{{status}}
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 | 28 | 29 | 30 | 31 | 32 | 33 |
masterdev
master dev
34 |
35 | 36 | 996.icu 37 | 38 | 39 |
Deploy
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 | 点击[![Deploy](https://www.herokucdn.com/deploy/button.svg)](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 | 通知发布日期:Mon Mar 4 14:23:45 CST 2019 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 |
47 | 48 | ShadowSocks服务器信息 49 | {% for server in servers %} 50 |

51 | 请支持免费ss账号的提供者:{{server['info']['name']}}
52 | 服务器通知:
{{server['info']['message']}}
53 |
54 |

    55 | {%set outer_loop = loop%} 56 | {% for s in server['data'] %} 57 | {%- if s['status'] > 0 -%} 58 |
  1. 59 | {{s['remarks']}} - {{s['status']}}ms 60 |
  2. 61 | {% endif %} 62 | {% endfor %} 63 |
64 |

65 | {% endfor %} 66 |

其他信息:

67 | 爬到的所有账号(包含大量失效账号)在这里
68 | SSR订阅地址:
右键点击本链接,选择复制连接地址
69 | JSON格式的订阅地址(每次随机从库中抽取一条json返回):右键点击本链接,选择复制连接地址 70 |
71 | 72 | 73 |
74 |

如果遇到了什么问题或有任何建议,请到我的 GitHub Issue 75 | 页面提供反馈:)

76 |

如果用着感觉挺好,不妨在朋友之间宣传宣传,然后到GitHub 77 | 链接加个星:)

78 |
79 | 80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
作者的博客Blogger (需爬墙)
给作者发 Email 反馈charl3s.xu@gmail.com 91 |
源码地址GitHub 链接
更新源码后访问次数{{counter}}
109 |

110 | 生日快乐!小宝儿~
111 | 点击查看生日礼物 112 |

113 |

114 | ..................................
115 | .    __________________          .
116 | .   ( 作者的对象:小胖儿~ )         .
117 | .    ------------------          .
118 | .        \                       .
119 | .         \                      .
120 | .          \ >()_                .
121 | .             (__)__ _ ___ __    .
122 | ..................................
123 | 
124 |
125 |

留言板

126 |
127 | 128 | 142 | 144 |
145 |

友情链接

146 |

147 | 感谢https://www.wishosting.com/提供免费被墙服务器 148 |

149 |
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 |
11 | 通知发布日期:Mon Mar 4 14:23:45 CST 2019 12 | 由于 Google plus 将于2019年4月2日关闭,而之前爬到的可用账号几乎都来源于 Google plus。
13 | 所以如果大家是自己搭的网站,请时刻关注本项目更新动态,并使用最新源码重新部署。
14 | 最后,如果有推荐的免费ss账号分享网站,不妨在这里提供一下,或者向作者发email:charl3s.xu@gmail.com
16 | 作者将在学习工作之余抽空研究并爬一下试试。 17 |
18 | 19 |
20 | 通知发布日期:Thu Nov 23 10:03:39 CST 2017 21 | 如果本网站无法访问,可以参考这个 Issue
22 | 获取最新信息,请持续关注本项目的 GitHub 仓库:) 23 |
24 | 25 |
26 | 通知发布日期:Fri Oct 27 19:55:17 CST 2017 27 |

重大更新

28 | 万众瞩目的验证账号可用性功能终于完成了,耗费了好多时间看ShadowSocks源码、改代码。
29 | 感谢大家的捐助,捐助信息越来越长了,我将它挪到了专门的捐助页面:)
30 | 这个业余时间的小作品已经比较完善了,GitHub也获得了超过100个Star:)
31 | 现在看来,基础功能都已经实现了,因此如果没有发现Bug,本项目将不会经常需要更新了:)
32 | 以后的更新将主要添加/替换ssr源、发布自建SSR账号
33 | 写了一篇博客,记录了一下这个小项目开发的记录,如果有兴趣的话可以点击这里看看:) 34 |
35 | 36 |
37 | 通知发布日期:Wed Oct 11 19:30:29 CST 201 38 |

当前已知问题

39 |

40 |

    41 |
  1. 没有做ss账号失效检测
  2. 42 |
  3. 非ssr的账号也可以放到订阅里面,只不过需要转变格式
  4. 43 |
  5. 输出的JSON格式的文件有问题,如果是非ssr账号,生成的配置文件包含无用信息,
    Python客户端会不认。如果想使用请删掉内容为""的条目
  6. 44 |
  7. 网站性能不太强
  8. 45 |
  9. 最近为了弄ssl证书把DNS弄得很乱,所以另起炉灶换了域名,新域名为ssr.pythonic.life, 46 |
    现在原域名会重定向到新域名。 如果域名都不好使,请使用备用网址: 备用链接
  10. 48 |
49 |

50 |

51 | 除了修复问题,下一步准备做: 52 |

    53 |
  1. 使用MySQL存储数据
  2. 54 |
  3. 添加留言版功能(当前的留言板其实就是我的GitHub Issue页面)
  4. 55 |
  5. 搞定ssl证书,弄https加密
  6. 56 |
57 |

58 |
59 | 60 | 61 |
62 | 通知发布日期:Mon Oct 9 19:54:22 CST 2017 63 | 逗比根据地写了一款小bash脚本,可以用来验证ssr账号可用性,推荐大家使用:)
64 | 链接在此
65 | 我近期比较忙,没有时间维护网站,如果有朋友可以一起维护,实现自动验证ssr可用性的话,欢迎帮忙:) 66 |
67 | 68 | 69 |
70 | 通知发布日期:Fri Sep 29 21:28:52 CST 2017 71 | 添加了https://freess.cx/的链接,代码日志记录系统改善,能回溯到所有问题所在,解决了所有已知错误。 72 |
73 | 74 | 75 |
76 | 通知发布日期:Fri Sep 29 21:27:50 CST 2017 77 | 代码进行了较大改造,减少代码量,增加了历史通知功能。 78 |
79 | 80 | 81 |
82 | 通知发布日期:Sat Sep 16 17:06:09 CST 2017 83 | 找到了一个免费VPS(可惜免费的服务器只能选择被墙的)
84 | 官方网址在这里:https://www.wishosting.com/ 85 | 具体教程在这里:http://www.138vps.com/go/?url=https://liyuans.com/archives/wishosting.html 87 |
88 | 89 | 90 |
91 | 92 | 通知发布日期:Sat Sep 16 16:42:43 CST 2017 93 | 94 | 从网上爬到的 ss 账号稳定性欠佳。我又找到了一些不错的 Hosts 源,分享给大家:
Windows 7/8/10 或 Linux 请使用我写的自动更新 Hosts 脚本;Android 96 | 请使用 一键 Go Hosts - NEW 97 |

98 |

请使用 ShadowSocksR 客户端,否则连不上别怪服务器不行 =_=

99 | 找到了一个可以免费获取的VPS,不过需要https://www.wishosting.com/ 100 | 下面是各操作系统的 SSR 客户端地址:
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 125 | 126 | 127 | 128 | 129 | 130 | 132 | 133 | 134 | 135 | 136 | 137 | 139 | 140 | 141 | 142 | 143 | 144 | 148 | 149 | 150 |
平台链接备注
LinuxLinux 111 | 客户端一键安装配置使用脚本命令行界面, Mac 也能用,使用方法见注释
Terminaltsocks安装使用教程
WindowsSSR C# 版 124 | Windows 推荐使用
Android Android 131 | 客户端
macOSShadowsocksX-NGMac 也能用 Linux 版的
iOS只有中国 App Store 账号, 美区 App Store 只有中国区 App Store的话会麻烦一些
151 |

152 |
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 | --------------------------------------------------------------------------------