├── varo-auth ├── requirements.txt ├── README.md ├── varo_auth.py └── example.py ├── doh_nginx.png ├── .gitignore ├── email ├── rdns_linode.png ├── mailinabox.sh ├── README.md └── mailconfig.py ├── varo ├── update-mutual.sh ├── admin.sh ├── update-dash.sh ├── backupns.sh ├── README.md ├── mutual.sh ├── apache2.conf └── dash.sh ├── tlsa ├── proxy-csv.sh ├── redirect-csv.sh ├── sql ├── addbackup.sh ├── README.md ├── mbackup.sh └── sbackup.sh ├── namebase ├── README.md ├── watchlist.py └── accept.py ├── sitecheck.sh ├── hsd ├── update.sh ├── README.md └── domains.py ├── redirecticann ├── addsld ├── proxyicann ├── hsdDiscord.sh ├── redirect-noinstall.sh ├── proxy-noinstall.sh ├── redirect-sld ├── redirect ├── newicann ├── proxy-sld ├── proxy ├── doh.md ├── new ├── git.sh ├── wp.sh ├── README.md └── LICENSE.txt /varo-auth/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | requests -------------------------------------------------------------------------------- /doh_nginx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/HEAD/doh_nginx.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | wallet.sh 3 | 4 | varo-auth/__pycache__/ 5 | 6 | pub.crt 7 | 8 | hsd/domains.csv 9 | -------------------------------------------------------------------------------- /email/rdns_linode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/HEAD/email/rdns_linode.png -------------------------------------------------------------------------------- /varo/update-mutual.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /var/www/html/dashboard 4 | echo "Updating git repo" 5 | git pull 6 | echo "Done" -------------------------------------------------------------------------------- /varo/admin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | email=$1 4 | echo "Making admin with email" 5 | echo $email 6 | 7 | # Add admin to database 8 | mysql -p varo -e "UPDATE users SET admin=1 WHERE email=\"$email\";" -------------------------------------------------------------------------------- /varo/update-dash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /var/www/html/dashboard 4 | echo "Updating git repo" 5 | git stash 6 | git pull 7 | export COMPOSER_ALLOW_SUPERUSER=1; 8 | cd etc 9 | composer install 10 | cd .. -------------------------------------------------------------------------------- /varo-auth/README.md: -------------------------------------------------------------------------------- 1 | # Varo Auth 2 | 3 | ## Python module 4 | Copy varo-auth.py to your project and import it. 5 | 6 | ## Python usage 7 | An example of how to use the module is in example.py 8 | 9 | Install the requirements with `python3 -m pip install -r requirements.txt` 10 | Then run `python3 example.py` to run the example. -------------------------------------------------------------------------------- /tlsa: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain=$1 4 | # Check if args passed 5 | if [ -z "$1" ] 6 | then 7 | # Ask for domain name 8 | echo "Domain name:" 9 | read domain 10 | 11 | fi 12 | 13 | echo "TLSA record:" 14 | echo -n "3 1 1 " && openssl x509 -in /etc/ssl/$domain.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 15 | -------------------------------------------------------------------------------- /proxy-csv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILE=$PWD/proxy-noinstall.sh 4 | if [ -f "$FILE" ]; then 5 | echo "$FILE exists." 6 | else 7 | echo "$FILE does not exist. Downloading..." 8 | wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/proxy-noinstall.sh 9 | chmod +x proxy-noinstall.sh 10 | fi 11 | 12 | while IFS="," read -r domain url 13 | do 14 | echo "" 15 | echo "Domain: $domain" 16 | echo "URL: $url" 17 | bash proxy-noinstall.sh $domain $url 18 | done < <(tail -n +2 $1) 19 | 20 | 21 | sudo systemctl restart nginx -------------------------------------------------------------------------------- /redirect-csv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILE=$PWD/redirect-noinstall.sh 4 | if [ -f "$FILE" ]; then 5 | echo "$FILE exists." 6 | else 7 | echo "$FILE does not exist. Downloading..." 8 | wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/redirect-noinstall.sh 9 | chmod +x redirect-noinstall.sh 10 | fi 11 | 12 | while IFS="," read -r domain url 13 | do 14 | echo "" 15 | echo "Domain: $domain" 16 | echo "URL: $url" 17 | bash redirect-noinstall.sh $domain $url 18 | done < <(tail -n +2 $1) 19 | 20 | 21 | sudo systemctl restart nginx -------------------------------------------------------------------------------- /varo-auth/varo_auth.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | def flask_login(request): 5 | dict = request.form.to_dict() 6 | keys = dict.keys() 7 | keys = list(keys)[0] 8 | keys = json.loads(keys) 9 | auth_request = keys['request'] 10 | return login(auth_request) 11 | 12 | def login(request): 13 | r = requests.get(f'https://auth.varo.domains/verify/{request}') 14 | r = r.json() 15 | if r['success'] == False: 16 | return False 17 | 18 | if 'data' in r: 19 | data = r['data'] 20 | if 'name' in data: 21 | return data['name'] 22 | return False -------------------------------------------------------------------------------- /sql/addbackup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Show master status 4 | SMS=/tmp/show_master_status.txt 5 | mysql -ANe "SHOW MASTER STATUS" > ${SMS} 6 | CURRENT_LOG=`cat ${SMS} | awk '{print $1}'` 7 | CURRENT_POS=`cat ${SMS} | awk '{print $2}'` 8 | echo "Retrieved LOG ${CURRENT_LOG}" 9 | echo "Retrieved POS ${CURRENT_POS}" 10 | 11 | echo "Exporting current database" 12 | echo "You might need to enter the password for the database" 13 | # Export all databases 14 | mysqldump -p pdns > pdns.sql 15 | mysqldump -p varo > varo.sql 16 | echo "Copy pdns.sql and varo.sql files to slave server" 17 | echo "Use password from first slave server command" -------------------------------------------------------------------------------- /email/mailinabox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | RESOLVER=152.69.186.119 3 | 4 | # This script will patch the Mail-in-a-Box system to allow for the use of Handshake domains. 5 | 6 | # Set dns resolver 7 | echo "nameserver $RESOLVER" > /etc/resolv.conf 8 | echo "nameserver $RESOLVER" > /var/spool/postfix/etc/resolv.conf 9 | 10 | # Replace the old mailconfig file to allow hns tlds 11 | mv mailinabox/management/mailconfig.py mailinabox/management/mailconfig.py.old 12 | # Download the new mailconfig file 13 | wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/email/mailconfig.py -P mailinabox/management/ 14 | 15 | systemctl stop mailinabox 16 | systemctl start mailinabox 17 | systemctl status mailinabox -------------------------------------------------------------------------------- /namebase/README.md: -------------------------------------------------------------------------------- 1 | # Namebase scripts 2 | 3 | 4 | ## Bulk accept all offers over a threshold 5 | 6 | Simply get your Namebase token then run this command replacing these variables 7 | - ``: the minimum offer you want to accept (eg. `10` for 10 HNS) 8 | - ``: your Namebase-main cookie 9 | 10 | ``` 11 | python3 accept.py --token --threshold 12 | ``` 13 | 14 | ## Bulk watch domains 15 | 16 | Create a txt file with 1 domain per line (eg. `domains.txt`) then run this command replacing these variables 17 | - ``: your Namebase-main cookie 18 | - ``: the path to your txt file 19 | 20 | ``` 21 | python3 watchlist.py --token --domains 22 | ``` -------------------------------------------------------------------------------- /sitecheck.sh: -------------------------------------------------------------------------------- 1 | # Ask user from which domain he wants to get the certificate 2 | echo "Please enter the domain name:" 3 | read domain 4 | 5 | if [ -z "$domain" ] 6 | then 7 | echo "You did not enter a domain name" 8 | exit 1 9 | fi 10 | 11 | resolver=152.69.186.119 12 | 13 | # Get the IP address of the domain 14 | ip=$(dig @$resolver +short $domain) 15 | echo "The IP address of $domain is $ip" 16 | 17 | 18 | # Get the certificate from the server 19 | openssl s_client -showcerts -connect $ip:443 -servername $domain < /dev/null 2>/dev/null | (while openssl x509 2>/dev/null; do true; done) > pub.crt 20 | echo "The certificate is:" 21 | cat pub.crt 22 | echo "The certificate TLSA is:" 23 | echo -n "3 1 1 " && openssl x509 -in pub.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 -------------------------------------------------------------------------------- /hsd/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | apikey="changeme" #! change this to your api key 4 | wallet="hot" #! change this to your wallet name 5 | 6 | # Set your DNS here. Example: 7 | dns="{\"type\": \"NS\",\"ns\": \"ns1.woodburn.\"},{\"type\": \"NS\",\"ns\": \"ns2.woodburn.\"}" 8 | 9 | 10 | # Get domains from file domains.txt 11 | while read -r line; do 12 | domains+=("$line") 13 | done < domains.txt 14 | 15 | # Generate param 16 | for domain in "${domains[@]}"; do 17 | batch+=("[\"UPDATE\", \"$domain\", {\"records\":[$dns]}]") 18 | done 19 | batch="[$(IFS=,; echo "${batch[*]}")]" 20 | echo "$batch" 21 | 22 | # Unlock wallet 23 | echo "Enter password for wallet $wallet" 24 | read -s pass 25 | hsw-cli unlock --id=$wallet $pass 120 --api-key=$apikey 26 | hsw-rpc selectwallet $wallet --api-key=$apikey 27 | 28 | # Send batch 29 | hsw-rpc sendbatch "$batch" --api-key=$apikey 30 | 31 | -------------------------------------------------------------------------------- /redirecticann: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain=$1 4 | url=$2 5 | 6 | # Check if args passed 7 | if [ -z "$1" ] 8 | then 9 | # Ask for domain name 10 | echo "Domain name:" 11 | read domain 12 | 13 | fi 14 | 15 | if [ -z "$2" ] 16 | then 17 | # Ask for domain name 18 | echo "URL:" 19 | read url 20 | 21 | fi 22 | 23 | # Install nginx 24 | sudo apt update 25 | sudo apt-get install nginx certbot python3-certbot-nginx -y 26 | # Setup NGINX config 27 | printf "server { 28 | listen 80; 29 | listen [::]:80; 30 | server_name $domain; 31 | return 301 $url; 32 | }" > /etc/nginx/sites-available/$domain 33 | sudo ln -s /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/$domain 34 | 35 | # LetsEncrypt SSL Certificate 36 | sudo certbot --nginx -d $domain 37 | 38 | # Add cert renewal to cron 39 | crontab -l > cron 40 | printf "0 12 * * * /usr/bin/certbot renew --quiet 41 | " > cron 42 | crontab cron 43 | rm cron 44 | 45 | # Restart to apply config file 46 | sudo systemctl restart nginx 47 | -------------------------------------------------------------------------------- /addsld: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Setup NGINX config 3 | printf "server { 4 | listen 80; 5 | listen [::]:80; 6 | root $PWD; 7 | index index.html; 8 | server_name $2.$1; 9 | 10 | location / { 11 | try_files \$uri \$uri/ @htmlext; 12 | } 13 | 14 | location ~ \.html$ { 15 | try_files \$uri =404; 16 | } 17 | 18 | location @htmlext { 19 | rewrite ^(.*)$ \$1.html last; 20 | } 21 | error_page 404 /404.html; 22 | location = /404.html { 23 | internal; 24 | } 25 | location = /.well-known/wallets/HNS { 26 | add_header Cache-Control 'must-revalidate'; 27 | add_header Content-Type text/plain; 28 | } 29 | listen 443 ssl; 30 | ssl_certificate /etc/ssl/$1.crt; 31 | ssl_certificate_key /etc/ssl/$1.key; 32 | } 33 | " > /etc/nginx/sites-available/$2.$1 34 | sudo ln -s /etc/nginx/sites-available/$2.$1 /etc/nginx/sites-enabled/$2.$1 35 | 36 | # Restart to apply config file 37 | sudo chmod a+rx $PWD 38 | sudo systemctl restart nginx -------------------------------------------------------------------------------- /sql/README.md: -------------------------------------------------------------------------------- 1 | # MariaDB-Instructions 2 | 3 | ## Master 4 | If this is the first slave to add to the master, run the following commands. 5 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/sql/mbackup.sh` 6 | `sudo chmod +x mbackup.sh` 7 | `sudo ./mbackup.sh` 8 | 9 | If you have already setup a slave, then run the following commands. 10 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/sql/addbackup.sh` 11 | `sudo chmod +x addbackup.sh` 12 | `sudo ./addbackup.sh` 13 | 14 | ## Slave 15 | First you need these config from the master setup 16 | + Master IP (Internal VPN or shared network is most secure) 17 | + Generated Password 18 | + Log 19 | + Pos 20 | 21 | You also need the two database exports (varo.sql and pdns.sql) in the directory you are when running the script. 22 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/sql/sbackup.sh` 23 | `sudo chmod +x sbackup.sh` 24 | `sudo ./sbackup.sh ` 25 | -------------------------------------------------------------------------------- /proxyicann: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain=$1 4 | url=$2 5 | 6 | # Check if args passed 7 | if [ -z "$1" ] 8 | then 9 | # Ask for domain name 10 | echo "Domain name:" 11 | read domain 12 | 13 | fi 14 | 15 | if [ -z "$2" ] 16 | then 17 | # Ask for domain name 18 | echo "URL:" 19 | read url 20 | 21 | fi 22 | 23 | # Install nginx 24 | sudo apt update 25 | sudo apt-get install nginx certbot python3-certbot-nginx -y 26 | # Setup NGINX config 27 | printf "server { 28 | listen 80; 29 | listen [::]:80; 30 | server_name $domain; 31 | proxy_ssl_server_name on; 32 | location / { 33 | proxy_set_header X-Real-IP \$remote_addr; 34 | proxy_pass $url; 35 | } 36 | }" > /etc/nginx/sites-available/$domain 37 | sudo ln -s /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/$domain 38 | 39 | # LetsEncrypt SSL Certificate 40 | sudo certbot --nginx -d $domain 41 | 42 | # Add cert renewal to cron 43 | crontab -l > cron 44 | printf "0 12 * * * /usr/bin/certbot renew --quiet 45 | " > cron 46 | crontab cron 47 | rm cron 48 | 49 | # Restart to apply config file 50 | sudo systemctl restart nginx 51 | -------------------------------------------------------------------------------- /varo/backupns.sh: -------------------------------------------------------------------------------- 1 | # Generate random password 2 | LOCALPASS=$(date +%s | sha256sum | base64 | head -c 32) 3 | 4 | # Add local login 5 | sudo mysql -e "CREATE USER 'mutual'@'localhost' IDENTIFIED BY '$LOCALPASS';" 6 | # Add perms 7 | sudo mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'mutual'@'localhost';" 8 | sudo mysql -e "FLUSH PRIVILEGES;" 9 | 10 | # Stop default DNS resolver 11 | systemctl disable --now systemd-resolved 12 | rm -rf /etc/resolv.conf 13 | echo "nameserver 1.1.1.1" > /etc/resolv.conf 14 | 15 | # Setup pdns with mariadb backend 16 | sudo apt-get install pdns-server pdns-backend-mysql -y 17 | 18 | # add gmysql config 19 | printf "launch+=gmysql 20 | gmysql-host=localhost 21 | gmysql-port=3306 22 | gmysql-user=mutual 23 | gmysql-password=$LOCALPASS 24 | gmysql-dbname=pdns 25 | gmysql-dnssec=yes" > /etc/powerdns/pdns.d/pdns.local.gmysql.conf 26 | 27 | # Make sure perms is correct 28 | chown pdns:pdns /etc/powerdns/pdns.d/pdns.local.gmysql.conf 29 | chmod 755 -R /etc/powerdns 30 | 31 | 32 | # Restart pdns 33 | systemctl restart pdns 34 | 35 | # Save password to file 36 | echo "LOCALPASS: $LOCALPASS" > $PWD/password.txt 37 | echo "Password saved to $PWD/password.txt" -------------------------------------------------------------------------------- /hsdDiscord.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | KEY="" 3 | DISCORD="" 4 | BLOCK=$(hsd-cli info --url=127.0.0.1 --api-key=$KEY | jq '.chain.height') 5 | INBOUND=$(hsd-cli info --url=127.0.0.1 --api-key=$KEY | jq '.pool.inbound') 6 | TYPES=$(hsd-cli rpc getpeerinfo --url=127.0.0.1 --api-key=$KEY | jq '. | map(.subver) | group_by(.) | map({key:.[0], value: length}) | sort_by(.key) | from_entries + {total: [.[].value] | add}') 7 | URL="$(hsd-cli rpc getpeerinfo --url=127.0.0.1 --api-key=$KEY | jq -r '.[]|select(.inbound==true)|.addr|sub(":.*$";"")' | curl -s -XPOST --data-binary @- "ipinfo.io/tools/summarize-ips?cli=1"|jq -r .reportUrl)" 8 | CLEANED=$(echo $TYPES | sed 's/.*{\(.*\)}.*$/\1/') 9 | CLEANED=$(echo $CLEANED | sed 's/"/ /g') 10 | CLEANED=$(echo $CLEANED | sed 's/,/\\n /g') 11 | JSON="{\"content\": \"Current Height: $BLOCK\nUsers connected to node: $INBOUND\",\"embeds\": [{\"title\": \"Current Connections\",\"description\": \"$CLEANED\",\"url\": \"$URL\",\"color\": null,\"footer\": {\"text\": \"PS click the title to get a map of connections\"}}],\"attachments\": []}" 12 | curl -H "Content-Type: application/json" -X POST -d "$JSON" $DISCORD -------------------------------------------------------------------------------- /redirect-noinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Setup NGINX config 4 | printf "server { 5 | listen 80; 6 | listen [::]:80; 7 | server_name $1 *.$1; 8 | return 301 $2; 9 | listen 443 ssl; 10 | ssl_certificate /etc/ssl/$1.crt; 11 | ssl_certificate_key /etc/ssl/$1.key; 12 | }" > /etc/nginx/sites-available/$1 13 | sudo ln -s /etc/nginx/sites-available/$1 /etc/nginx/sites-enabled/$1 14 | 15 | #generate ssl certificate 16 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 17 | -keyout cert.key -out cert.crt -extensions ext -config \ 18 | <(echo "[req]"; 19 | echo distinguished_name=req; 20 | echo "[ext]"; 21 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 22 | echo "extendedKeyUsage=serverAuth"; 23 | echo "basicConstraints=critical,CA:FALSE"; 24 | echo "subjectAltName=DNS:$1,DNS:*.$1"; 25 | ) -subj "/CN=*.$1" 26 | 27 | # Print TLSA record and store in file in case of lost output 28 | echo "Add this TLSA Record to your DNS:" 29 | echo -n "3 1 1 " && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 30 | 31 | sudo mv cert.key /etc/ssl/$1.key 32 | sudo mv cert.crt /etc/ssl/$1.crt -------------------------------------------------------------------------------- /sql/mbackup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Generate random password 4 | password=$(date +%s | sha256sum | base64 | head -c 32) 5 | 6 | # Create backup user 7 | mysql -e "CREATE USER 'pdnsbackup'@'%' IDENTIFIED BY '$password';" 8 | 9 | # Grant privileges 10 | mysql -e "GRANT REPLICATION SLAVE ON *.* TO 'pdnsbackup'@'%';" 11 | 12 | # Flush privileges 13 | mysql -e "FLUSH PRIVILEGES;" 14 | 15 | # Show master status 16 | SMS=/tmp/show_master_status.txt 17 | mysql -ANe "SHOW MASTER STATUS" > ${SMS} 18 | CURRENT_LOG=`cat ${SMS} | awk '{print $1}'` 19 | CURRENT_POS=`cat ${SMS} | awk '{print $2}'` 20 | echo "Retrieved LOG ${CURRENT_LOG}" 21 | echo "Retrieved POS ${CURRENT_POS}" 22 | 23 | echo "Backup user created with password: ${password}" 24 | 25 | echo "Backup user created with password: ${password}" > backup_settings.txt 26 | echo "Current log: ${CURRENT_LOG}" >> backup_settings.txt 27 | echo "Current pos: ${CURRENT_POS}" >> backup_settings.txt 28 | 29 | echo "If you forget these credentials, you can find them in backup_settings.txt" 30 | 31 | echo "Exporting current database" 32 | echo "You might need to enter the password for the database" 33 | # Exporting all databases 34 | mysqldump -p pdns > pdns.sql 35 | mysqldump -p varo > varo.sql 36 | 37 | echo "Copy pdns.sql and varo.sql files to slave server" -------------------------------------------------------------------------------- /proxy-noinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Setup NGINX config 3 | printf "server { 4 | listen 80; 5 | listen [::]:80; 6 | server_name $1 *.$1; 7 | proxy_ssl_server_name on; 8 | location / { 9 | proxy_set_header X-Real-IP \$remote_addr; 10 | proxy_pass $2; 11 | } 12 | 13 | listen 443 ssl; 14 | ssl_certificate /etc/ssl/$1.crt; 15 | ssl_certificate_key /etc/ssl/$1.key; 16 | }" > /etc/nginx/sites-available/$1 17 | sudo ln -s /etc/nginx/sites-available/$1 /etc/nginx/sites-enabled/$1 18 | 19 | #generate ssl certificate 20 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 21 | -keyout cert.key -out cert.crt -extensions ext -config \ 22 | <(echo "[req]"; 23 | echo distinguished_name=req; 24 | echo "[ext]"; 25 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 26 | echo "extendedKeyUsage=serverAuth"; 27 | echo "basicConstraints=critical,CA:FALSE"; 28 | echo "subjectAltName=DNS:$1,DNS:*.$1"; 29 | ) -subj "/CN=*.$1" 30 | 31 | # Print TLSA record and store in file in case of lost output 32 | echo "Add this TLSA Record to your DNS for HNS domain $1:" 33 | echo -n "3 1 1 " && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 34 | sudo mv cert.key /etc/ssl/$1.key 35 | sudo mv cert.crt /etc/ssl/$1.crt -------------------------------------------------------------------------------- /namebase/watchlist.py: -------------------------------------------------------------------------------- 1 | # This script will read a list of domains from a file and add them to the users NB watchlist. 2 | 3 | import requests 4 | import argparse 5 | from datetime import datetime, timedelta 6 | import json 7 | 8 | if __name__ == "__main__": 9 | parser = argparse.ArgumentParser(description="Bulk add domains to watchlist in namebase") 10 | parser.add_argument("--token", required=True, help="Token for authentication") 11 | parser.add_argument("--file", required=True, help="File containing list of domains to add to watchlist") 12 | args = parser.parse_args() 13 | 14 | token = args.token 15 | filename = args.file 16 | 17 | headers = { 18 | "authority": "www.namebase.io", 19 | "accept": "application/json", 20 | "content-type": "application/json", 21 | "cookie": f"namebase-main={token}" 22 | } 23 | 24 | with open(filename) as f: 25 | domains = f.readlines() 26 | domains = [x.strip() for x in domains] 27 | 28 | for domain in domains: 29 | url = f"https://www.namebase.io/api/domains/watch/{domain}" 30 | 31 | response = requests.post(url, headers=headers, data=json.dumps({})) 32 | if response.status_code == 200: 33 | print(f"Added {domain} to watchlist") 34 | else: 35 | print(f"Error adding {domain} to watchlist") 36 | print(response.text) -------------------------------------------------------------------------------- /sql/sbackup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ip=$1 4 | password=$2 5 | log=$3 6 | pos=$4 7 | 8 | oldbind="bind-address = 127.0.0.1" 9 | newbind="bind-address = 0.0.0.0" 10 | 11 | apt-get install mariadb-server mariadb-client -y 12 | 13 | # Replace old bind address with new bind address 14 | sed -i "s/$oldbind/$newbind/g" /etc/mysql/mariadb.conf.d/50-server.cnf 15 | 16 | # Add replication settings 17 | 18 | #! Change server-id to be unique for each server 19 | echo "server-id = 2" >> /etc/mysql/mariadb.conf.d/50-server.cnf 20 | echo "log_bin = /var/log/mysql/mysql-bin.log" >> /etc/mysql/mariadb.conf.d/50-server.cnf 21 | echo "log_bin_index =/var/log/mysql/mysql-bin.log.index" >> /etc/mysql/mariadb.conf.d/50-server.cnf 22 | echo "relay_log = /var/log/mysql/mysql-relay-bin" >> /etc/mysql/mariadb.conf.d/50-server.cnf 23 | echo "relay_log_index = /var/log/mysql/mysql-relay-bin.index" >> /etc/mysql/mariadb.conf.d/50-server.cnf 24 | 25 | # Restart MySQL 26 | systemctl restart mariadb 27 | 28 | # Stop slave 29 | mysql -e "STOP SLAVE;" 30 | 31 | # Change master settings 32 | mysql -e "CHANGE MASTER TO MASTER_HOST='$ip', MASTER_USER='pdnsbackup', MASTER_PASSWORD='$password', MASTER_LOG_FILE='$log', MASTER_LOG_POS=$pos;" 33 | 34 | # Create database 35 | mysql -e "CREATE DATABASE pdns;" 36 | mysql -e "CREATE DATABASE varo;" 37 | 38 | # Import all databases 39 | mysql -p pdns < "$PWD/pdns.sql" 40 | mysql -p varo < "$PWD/varo.sql" 41 | 42 | # start slave 43 | mysql -e "START SLAVE;" -------------------------------------------------------------------------------- /hsd/README.md: -------------------------------------------------------------------------------- 1 | # HSD Scripts 2 | 3 | This directory contains scripts for HSD. 4 | 5 | ## update.sh 6 | This script is used to send batch updates for a list of domains. 7 | To use it, create a file called `domains.txt` with a list of domains to update. 8 | 9 | Edit the `update.sh` script to set: 10 | - `apikey` to your HSD API key 11 | - `wallet` to the name of your HSD wallet containing the domains 12 | - `dns` to the DNS records to update the domains with 13 | 14 | Then run `./update.sh` to send the updates. 15 | 16 | ## Save domain info 17 | These scripts are used to save the domain info from your wallet 18 | 19 | Format options are 20 | - name -> domain name 21 | - expiry -> estimated expiry 22 | - expiryBlock -> Block domain expires 23 | - value -> Amount paid in auction (in HNS) 24 | - maxBid -> Highest bid in auction (in HNS) 25 | - openHeight -> Height the auction opened 26 | - state -> Domain state (usually only `CLOSED`) 27 | 28 | ### Names only 29 | This only saves the domain names in 1 domain per line format. 30 | 31 | ```sh 32 | python3 domains.py --api-key --wallet --output-file domains.csv 33 | ``` 34 | 35 | ### Names + , + expiry 36 | This saves the domain names in 1 domain per line format with the expiry date in seconds since epoch. 37 | ! WARNING 38 | Expiries are only estimates as it depends on how long each block is. 39 | Always renew well before the expiry. 40 | 41 | ```sh 42 | python3 domains.py --api-key --wallet --output-file domains.csv --format '{name},{expiry}' 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /redirect-sld: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain=$1 4 | url=$2 5 | 6 | # Check if args passed 7 | if [ -z "$1" ] 8 | then 9 | # Ask for domain name 10 | echo "Domain name:" 11 | read domain 12 | 13 | fi 14 | 15 | if [ -z "$2" ] 16 | then 17 | # Ask for domain name 18 | echo "URL:" 19 | read url 20 | 21 | fi 22 | 23 | # Install nginx 24 | sudo apt update 25 | sudo apt install nginx -y 26 | # Setup NGINX config 27 | printf "server { 28 | listen 80; 29 | listen [::]:80; 30 | server_name $domain; 31 | return 301 $url; 32 | listen 443 ssl; 33 | ssl_certificate /etc/ssl/$domain.crt; 34 | ssl_certificate_key /etc/ssl/$domain.key; 35 | }" > /etc/nginx/sites-available/$domain 36 | sudo ln -s /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/$domain 37 | 38 | #generate ssl certificate 39 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 40 | -keyout cert.key -out cert.crt -extensions ext -config \ 41 | <(echo "[req]"; 42 | echo distinguished_name=req; 43 | echo "[ext]"; 44 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 45 | echo "extendedKeyUsage=serverAuth"; 46 | echo "basicConstraints=critical,CA:FALSE"; 47 | echo "subjectAltName=DNS:$domain,DNS:*.$domain"; 48 | ) -subj "/CN=*.$domain" 49 | 50 | # Print TLSA record and store in file in case of lost output 51 | echo "Add this TLSA Record to your DNS:" 52 | echo -n "3 1 1 " && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 53 | 54 | sudo mv cert.key /etc/ssl/$domain.key 55 | sudo mv cert.crt /etc/ssl/$domain.crt 56 | 57 | # Restart to apply config file 58 | sudo systemctl restart nginx 59 | -------------------------------------------------------------------------------- /redirect: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain=$1 4 | url=$2 5 | 6 | # Check if args passed 7 | if [ -z "$1" ] 8 | then 9 | # Ask for domain name 10 | echo "Domain name:" 11 | read domain 12 | 13 | fi 14 | 15 | if [ -z "$2" ] 16 | then 17 | # Ask for domain name 18 | echo "URL:" 19 | read url 20 | 21 | fi 22 | 23 | # Install nginx 24 | sudo apt update 25 | sudo apt install nginx -y 26 | # Setup NGINX config 27 | printf "server { 28 | listen 80; 29 | listen [::]:80; 30 | server_name $domain *.$domain; 31 | return 301 $url; 32 | listen 443 ssl; 33 | ssl_certificate /etc/ssl/$domain.crt; 34 | ssl_certificate_key /etc/ssl/$domain.key; 35 | }" > /etc/nginx/sites-available/$domain 36 | sudo ln -s /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/$domain 37 | 38 | #generate ssl certificate 39 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 40 | -keyout cert.key -out cert.crt -extensions ext -config \ 41 | <(echo "[req]"; 42 | echo distinguished_name=req; 43 | echo "[ext]"; 44 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 45 | echo "extendedKeyUsage=serverAuth"; 46 | echo "basicConstraints=critical,CA:FALSE"; 47 | echo "subjectAltName=DNS:$domain,DNS:*.$domain"; 48 | ) -subj "/CN=*.$domain" 49 | 50 | # Print TLSA record and store in file in case of lost output 51 | echo "Add this TLSA Record to your DNS:" 52 | echo -n "3 1 1 " && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 53 | 54 | sudo mv cert.key /etc/ssl/$domain.key 55 | sudo mv cert.crt /etc/ssl/$domain.crt 56 | 57 | # Restart to apply config file 58 | sudo systemctl restart nginx 59 | -------------------------------------------------------------------------------- /newicann: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain=$1 4 | # Check if args passed 5 | if [ -z "$1" ] 6 | then 7 | # Ask for domain name 8 | echo "Domain name:" 9 | read domain 10 | 11 | fi 12 | 13 | echo "Location to files (default current directory):" 14 | echo "Please note that the location must be readable by the nginx user." 15 | echo "For best results use a directory in /var/www" 16 | read location 17 | 18 | # if location is empty, use current directory 19 | if [ -z "$location" ] 20 | then 21 | location=$PWD 22 | fi 23 | 24 | # Install nginx 25 | sudo apt update 26 | sudo apt-get install nginx certbot python3-certbot-nginx -y 27 | # Setup NGINX config 28 | printf "server { 29 | listen 80; 30 | listen [::]:80; 31 | root $location; 32 | index index.html; 33 | server_name $domain; 34 | 35 | location / { 36 | try_files \$uri \$uri/ @htmlext; 37 | } 38 | 39 | location ~ \.html$ { 40 | try_files \$uri =404; 41 | } 42 | 43 | location @htmlext { 44 | rewrite ^(.*)$ \$1.html last; 45 | } 46 | error_page 404 /404.html; 47 | location = /404.html { 48 | internal; 49 | } 50 | location = /.well-known/wallets/HNS { 51 | add_header Cache-Control 'must-revalidate'; 52 | add_header Content-Type text/plain; 53 | } 54 | } 55 | " > /etc/nginx/sites-available/$domain 56 | sudo ln -s /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/$domain 57 | 58 | # LetsEncrypt SSL Certificate 59 | sudo certbot --nginx -d $domain 60 | 61 | # Add cert renewal to cron 62 | crontab -l > cron 63 | printf "0 12 * * * /usr/bin/certbot renew --quiet 64 | " > cron 65 | crontab cron 66 | rm cron 67 | 68 | # Restart to apply config file 69 | sudo chmod a+rx $location 70 | sudo systemctl restart nginx -------------------------------------------------------------------------------- /proxy-sld: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain=$1 4 | url=$2 5 | 6 | # Check if args passed 7 | if [ -z "$1" ] 8 | then 9 | # Ask for domain name 10 | echo "Domain name:" 11 | read domain 12 | 13 | fi 14 | 15 | if [ -z "$2" ] 16 | then 17 | # Ask for domain name 18 | echo "URL:" 19 | read url 20 | 21 | fi 22 | 23 | # Install nginx 24 | sudo apt update 25 | sudo apt install nginx -y 26 | 27 | # Setup NGINX config 28 | printf "server { 29 | listen 80; 30 | listen [::]:80; 31 | server_name $domain; 32 | proxy_ssl_server_name on; 33 | location / { 34 | proxy_set_header X-Real-IP \$remote_addr; 35 | proxy_pass $url; 36 | } 37 | 38 | listen 443 ssl; 39 | ssl_certificate /etc/ssl/$domain.crt; 40 | ssl_certificate_key /etc/ssl/$domain.key; 41 | }" > /etc/nginx/sites-available/$domain 42 | sudo ln -s /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/$domain 43 | 44 | #generate ssl certificate 45 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 46 | -keyout cert.key -out cert.crt -extensions ext -config \ 47 | <(echo "[req]"; 48 | echo distinguished_name=req; 49 | echo "[ext]"; 50 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 51 | echo "extendedKeyUsage=serverAuth"; 52 | echo "basicConstraints=critical,CA:FALSE"; 53 | echo "subjectAltName=DNS:$domain,DNS:*.$domain"; 54 | ) -subj "/CN=*.$domain" 55 | 56 | # Print TLSA record and store in file in case of lost output 57 | echo "Add this TLSA Record to your DNS:" 58 | echo -n "3 1 1 " && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 59 | 60 | sudo mv cert.key /etc/ssl/$domain.key 61 | sudo mv cert.crt /etc/ssl/$domain.crt 62 | 63 | # Restart to apply config file 64 | sudo systemctl restart nginx 65 | -------------------------------------------------------------------------------- /proxy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain=$1 4 | url=$2 5 | 6 | # Check if args passed 7 | if [ -z "$1" ] 8 | then 9 | # Ask for domain name 10 | echo "Domain name:" 11 | read domain 12 | 13 | fi 14 | 15 | if [ -z "$2" ] 16 | then 17 | # Ask for domain name 18 | echo "URL:" 19 | read url 20 | 21 | fi 22 | 23 | # Install nginx 24 | sudo apt update 25 | sudo apt install nginx -y 26 | 27 | # Setup NGINX config 28 | printf "server { 29 | listen 80; 30 | listen [::]:80; 31 | server_name $domain *.$domain; 32 | proxy_ssl_server_name on; 33 | location / { 34 | proxy_set_header X-Real-IP \$remote_addr; 35 | proxy_pass $url; 36 | } 37 | 38 | listen 443 ssl; 39 | ssl_certificate /etc/ssl/$domain.crt; 40 | ssl_certificate_key /etc/ssl/$domain.key; 41 | }" > /etc/nginx/sites-available/$domain 42 | sudo ln -s /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/$domain 43 | 44 | #generate ssl certificate 45 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 46 | -keyout cert.key -out cert.crt -extensions ext -config \ 47 | <(echo "[req]"; 48 | echo distinguished_name=req; 49 | echo "[ext]"; 50 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 51 | echo "extendedKeyUsage=serverAuth"; 52 | echo "basicConstraints=critical,CA:FALSE"; 53 | echo "subjectAltName=DNS:$domain,DNS:*.$domain"; 54 | ) -subj "/CN=*.$domain" 55 | 56 | # Print TLSA record and store in file in case of lost output 57 | echo "Add this TLSA Record to your DNS:" 58 | echo -n "3 1 1 " && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 59 | 60 | sudo mv cert.key /etc/ssl/$domain.key 61 | sudo mv cert.crt /etc/ssl/$domain.crt 62 | 63 | # Restart to apply config file 64 | sudo systemctl restart nginx 65 | -------------------------------------------------------------------------------- /varo-auth/example.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template_string, request, make_response 2 | import json 3 | import requests 4 | import secrets 5 | import varo_auth 6 | 7 | 8 | app = Flask(__name__) 9 | cookie = [] 10 | 11 | @app.route('/') 12 | def index(): 13 | if request.cookies.get('test_auth') != None: 14 | auth_cookie = request.cookies.get('test_auth') 15 | for i in cookie: 16 | if i['cookie'] == auth_cookie: 17 | return render_template_string(f''' 18 |

Index Page

19 |

Welcome {i['name']}

20 | ''') 21 | 22 | 23 | return render_template_string(''' 24 | 25 |

Index Page

26 | 27 | 28 | 36 | 37 | ''') 38 | 39 | @app.route('/auth', methods=['POST']) 40 | def auth(): 41 | global cookie 42 | auth = varo_auth.flask_login(request) 43 | if auth == False: 44 | return render_template_string("Error") 45 | resp = make_response(render_template_string("Success")) 46 | # Gen cookie 47 | auth_cookie = secrets.token_hex(12 // 2) 48 | cookie.append({'name': auth, 'cookie': auth_cookie}) 49 | resp.set_cookie('test_auth', auth_cookie) 50 | return resp 51 | 52 | 53 | 54 | if __name__ == '__main__': 55 | app.run(debug=True) 56 | -------------------------------------------------------------------------------- /doh.md: -------------------------------------------------------------------------------- 1 | # DoH Server 2 | 3 | ## Prerequisites 4 | - HSD installed and running 5 | - HSD DNS resolver enabled and listening 6 | You should be able to run `dig @ -p 5350 nathan.woodburn` and get a response with an A record. 7 | - Docker installed and running 8 | - NGINX or another proxy server installed and running 9 | - A ICANN domain that you have access to and that has a valid SSL certificate 10 | 11 | ## Setup 12 | Create a docker compose file with the following contents: 13 | ```yaml 14 | version: '3.7' 15 | 16 | # Connect to the same network as nginx proxy to allow proxying 17 | # Not required if you are using a proxy that is not running inside docker 18 | networks: 19 | nginx: 20 | external: true 21 | 22 | services: 23 | doh-server: 24 | image: satishweb/doh-server 25 | hostname: doh-server 26 | restart: always 27 | networks: 28 | - nginx 29 | ports: 30 | - "8053:8053" # Not needed if you are using a proxy that is running inside docker 31 | environment: 32 | DEBUG: "0" 33 | UPSTREAM_DNS_SERVER: "udp:10.2.1.15:5350" 34 | # Replace with 10.2.1.15 with the IP of your HSD server and 5350 with the port you are using for the HSD DNS resolver 35 | DOH_HTTP_PREFIX: "/dns-query" 36 | DOH_SERVER_LISTEN: ":8053" 37 | DOH_SERVER_TIMEOUT: "10" 38 | DOH_SERVER_TRIES: "3" 39 | DOH_SERVER_VERBOSE: "true" 40 | 41 | ``` 42 | 43 | 44 | Setup NGINX to proxy to the docker container. 45 | For example here is the config I use in NGINX Proxy Manager: 46 | ![NGINX Proxy Manager Config](doh_nginx.png) 47 | 48 | Make sure you have a valid SSL certificate for the domain you are using. 49 | 50 | 51 | ## Test 52 | You can test that it is working by running this command replacing `doh.hnshosting.au` with your domain. 53 | 54 | ```sh 55 | curl -H 'accept: application/dns-json' 'https://doh.hnshosting.au/dns-query?name=nathan.woodburn&type=A' | jq . 56 | ``` 57 | -------------------------------------------------------------------------------- /hsd/domains.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import argparse 3 | from datetime import datetime, timedelta 4 | 5 | def get_domains(apikey, wallet): 6 | url = f"http://x:{apikey}@127.0.0.1:12039/wallet/{wallet}/name?own=true" 7 | try: 8 | response = requests.get(url) 9 | response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx) 10 | return response.json() 11 | 12 | except requests.exceptions.RequestException as e: 13 | print(f"Error making the request: {e}") 14 | return [] 15 | 16 | def save_to_file(lines, output_file): 17 | with open(output_file, "w") as file: 18 | for line in lines: 19 | file.write(line+'\n') 20 | 21 | 22 | 23 | def main(): 24 | parser = argparse.ArgumentParser(description="Retrieve and save domain names from API") 25 | parser.add_argument("--api-key", required=True, help="API key for authentication") 26 | parser.add_argument("--wallet", default="default", help="Wallet name") 27 | parser.add_argument("--format", default="{name}", help="Format of file to output") 28 | parser.add_argument("--output-file", default="domains.csv", help="Output file for saving domains") 29 | args = parser.parse_args() 30 | 31 | apikey = args.api_key 32 | wallet = args.wallet 33 | format = str(args.format) 34 | 35 | domains = get_domains(apikey, wallet) 36 | lines = [format.replace("{","").replace("}","")] 37 | for domain in domains: 38 | line = format.replace("{name}",domain['name']) 39 | expiry = "N/A" 40 | expiryBlock = "N/A" 41 | if 'daysUntilExpire' in domain['stats']: 42 | days = domain['stats']['daysUntilExpire'] 43 | # Convert to dateTime 44 | expiry = datetime.now() + timedelta(days=days) 45 | expiry = expiry.strftime("%d/%m/%Y %H:%M:%S") 46 | expiryBlock = str(domain['stats']['renewalPeriodEnd']) 47 | 48 | line = line.replace("{expiry}",expiry) 49 | line = line.replace("{state}",domain['state']) 50 | line = line.replace("{expiryBlock}",expiryBlock) 51 | line = line.replace("{value}",str(domain['value']/1000000)) 52 | line = line.replace("{maxBid}",str(domain['highest']/1000000)) 53 | line = line.replace("{openHeight}",str(domain['height'])) 54 | lines.append(line) 55 | 56 | save_to_file(lines, args.output_file) 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /new: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain=$1 4 | # Check if args passed 5 | if [ -z "$1" ] 6 | then 7 | # Ask for domain name 8 | echo "Domain name:" 9 | read domain 10 | 11 | fi 12 | 13 | echo "Location to files (default current directory):" 14 | echo "Please note that the location must be readable by the nginx user." 15 | echo "For best results use a directory in /var/www" 16 | read location 17 | 18 | # if location is empty, use current directory 19 | if [ -z "$location" ] 20 | then 21 | location=$PWD 22 | fi 23 | 24 | # Install nginx 25 | sudo apt update 26 | sudo apt install nginx -y 27 | # Setup NGINX config 28 | printf "server { 29 | listen 80; 30 | listen [::]:80; 31 | root $location; 32 | index index.html; 33 | server_name $domain *.$domain; 34 | 35 | location / { 36 | try_files \$uri \$uri/ @htmlext; 37 | } 38 | 39 | location ~ \.html$ { 40 | try_files \$uri =404; 41 | } 42 | 43 | location @htmlext { 44 | rewrite ^(.*)$ \$1.html last; 45 | } 46 | error_page 404 /404.html; 47 | location = /404.html { 48 | internal; 49 | } 50 | location = /.well-known/wallets/HNS { 51 | add_header Cache-Control 'must-revalidate'; 52 | add_header Content-Type text/plain; 53 | } 54 | listen 443 ssl; 55 | ssl_certificate /etc/ssl/$domain.crt; 56 | ssl_certificate_key /etc/ssl/$domain.key; 57 | } 58 | " > /etc/nginx/sites-available/$domain 59 | sudo ln -s /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/$domain 60 | 61 | #generate ssl certificate 62 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 63 | -keyout cert.key -out cert.crt -extensions ext -config \ 64 | <(echo "[req]"; 65 | echo distinguished_name=req; 66 | echo "[ext]"; 67 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 68 | echo "extendedKeyUsage=serverAuth"; 69 | echo "basicConstraints=critical,CA:FALSE"; 70 | echo "subjectAltName=DNS:$domain,DNS:*.$domain"; 71 | ) -subj "/CN=*.$domain" 72 | 73 | # Print TLSA record and store in file in case of lost output 74 | echo "Add this TLSA Record to your DNS:" 75 | echo -n "3 1 1 " && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 76 | 77 | sudo mv cert.key /etc/ssl/$domain.key 78 | sudo mv cert.crt /etc/ssl/$domain.crt 79 | 80 | # Restart to apply config file 81 | sudo chmod a+rx $location 82 | sudo systemctl restart nginx -------------------------------------------------------------------------------- /git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is used to setup nginx for a static website using files from a git repository. 4 | # Make sure the git repo has an `index.html` and `404.html` file. 5 | 6 | # Usage ./git.sh [domain] [git repo url] 7 | # Example ./git.sh nathan.woodburn https://github.com/Nathanwoodburn/Nathanwoodburn.github.io.git 8 | 9 | # Variables 10 | domain=$1 11 | git_repo=$2 12 | 13 | # Check if domain name is set 14 | if [ -z "$1" ] 15 | then 16 | echo "Domain name:" 17 | read domain 18 | fi 19 | 20 | # Check if git repo is set 21 | if [ -z "$2" ] 22 | then 23 | echo "Git repo:" 24 | read git_repo 25 | fi 26 | 27 | # Check if nginx is installed 28 | if ! [ -x "$(command -v nginx)" ]; then 29 | sudo apt update 30 | sudo apt install nginx -y 31 | fi 32 | 33 | # Clone git repo 34 | git clone $git_repo /var/www/$domain 35 | 36 | 37 | # Setup NGINX config 38 | printf "server { 39 | listen 80; 40 | listen [::]:80; 41 | root /var/www/$domain; 42 | index index.html; 43 | server_name $domain *.$domain; 44 | 45 | location / { 46 | try_files \$uri \$uri/ @htmlext; 47 | } 48 | 49 | location ~ \.html$ { 50 | try_files \$uri =404; 51 | } 52 | 53 | location @htmlext { 54 | rewrite ^(.*)$ \$1.html last; 55 | } 56 | error_page 404 /404.html; 57 | location = /404.html { 58 | internal; 59 | } 60 | location = /.well-known/wallets/HNS { 61 | add_header Cache-Control 'must-revalidate'; 62 | add_header Content-Type text/plain; 63 | } 64 | listen 443 ssl; 65 | ssl_certificate /etc/ssl/$domain.crt; 66 | ssl_certificate_key /etc/ssl/$domain.key; 67 | } 68 | " > /etc/nginx/sites-available/$domain 69 | sudo ln -s /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/$domain 70 | 71 | #generate ssl certificate 72 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 73 | -keyout cert.key -out cert.crt -extensions ext -config \ 74 | <(echo "[req]"; 75 | echo distinguished_name=req; 76 | echo "[ext]"; 77 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 78 | echo "extendedKeyUsage=serverAuth"; 79 | echo "basicConstraints=critical,CA:FALSE"; 80 | echo "subjectAltName=DNS:$domain,DNS:*.$domain"; 81 | ) -subj "/CN=*.$domain" 82 | 83 | TLSA=$(echo -n "3 1 1 " && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32) 84 | 85 | echo "TLSA: $TLSA" 86 | 87 | sudo mv cert.key /etc/ssl/$domain.key 88 | sudo mv cert.crt /etc/ssl/$domain.crt 89 | 90 | # Restart to apply config file 91 | sudo systemctl restart nginx -------------------------------------------------------------------------------- /email/README.md: -------------------------------------------------------------------------------- 1 | # Email Hosting 2 | 3 | ## Requirements 4 | - A ICANN second level domain (SLD) eg. `email.com` 5 | - A server with 6 | - IP address that supports reverse DNS 7 | - At least 1GB of RAM 8 | - These ports open 9 | - 22 (SSH) 10 | - 25 (SMTP) 11 | - 53 (DNS for the ICANN domain) both TCP and UDP 12 | - 80 (HTTP) 13 | - 443 (HTTPS) 14 | - 465 (SMTPS) 15 | - 993 (IMAPS) 16 | - 995 (POP3S) 17 | - 4190 (Sieve) 18 | 19 | 20 | 21 | For this guide we will be using 22 | - `example.com` as the ICANN domain 23 | - `exampledomain` as the HNS domain 24 | - `1.2.3.4` as the server IP 25 | 26 | 27 | ## Step 1: Prep server 28 | Change hostname of server to your domain name + sub. 29 | ``` 30 | sudo echo "mail.example.com" > /etc/hostname 31 | ``` 32 | 33 | Add the hostname to hosts 34 | ``` 35 | sudo nano /etc/hosts 36 | ``` 37 | Add the following line to the end of the file 38 | ```bash 39 | 127.0.0.1 mail.example.com 40 | ``` 41 | 42 | ## Step 2: Prep domain 43 | Add two glue records to your ICANN domain 44 | - `ns1.example.com` pointing to `1.2.3.4` 45 | - `ns2.example.com` pointing to `1.2.3.4` 46 | 47 | Add the following NS records for your ICANN domain 48 | - `ns1.example.com` 49 | - `ns2.example.com` 50 | 51 | ## Step 3: Install mail in a box 52 | Run the following command to install mail in a box 53 | ```bash 54 | curl -s https://mailinabox.email/setup.sh | sudo -E bash 55 | ``` 56 | 57 | Answer the questions and wait for the install to finish. 58 | Run the following command to patch the install to work with HNS 59 | 60 | ```bash 61 | curl -s https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/email/mailinabox.sh | sudo -E bash 62 | ``` 63 | 64 | ## Step 4: Add Reverse DNS 65 | Add a reverse DNS record for your server IP pointing to your ICANN domain, in this case `mail.example.com` 66 | For example like so (but for the IPv6 as well) 67 | ![RDNS Setup for linode](rdns_linode.png) 68 | 69 | ## Step 5: Finish setup 70 | In the admin panel (accessible at `https://mail.example.com/admin`) 71 | 72 | Go to the TLS (SSL) Certificates tab and click the "Provision" button (usually requires 2 presses to provision all the certs) 73 | 74 | ## Adding a domain 75 | Simply add a user/alias with the domain eg. `nathan@woodburn` 76 | After adding the user/alias, go to external DNS and get the DNS records to add to your DNS for the HNS domain. 77 | You will need the MX and preferably the SPF records. 78 | 79 | I would recommend having your main user a ICANN domain to allow sending emails to other email providers. 80 | Then use aliases for the HNS domains as direct HNS accounts are a bit buggy with the webmail. -------------------------------------------------------------------------------- /namebase/accept.py: -------------------------------------------------------------------------------- 1 | # This script will automatically accept all pending transactions in your namebase account over a certain threshold. 2 | 3 | 4 | import requests 5 | import argparse 6 | from datetime import datetime, timedelta 7 | import json 8 | 9 | def get_pending(token,page=0): 10 | url = f"https://www.namebase.io/api/v0/offers/received?offset={page}&sortKey=createdAt&sortDirection=asc&showHidden=true" 11 | headers = { 12 | "authority": "www.namebase.io", 13 | "accept": "application/json", 14 | "content-type": "application/json", 15 | "cookie": f"namebase-main={token}" 16 | } 17 | return requests.get(url, headers=headers).json() 18 | 19 | if __name__ == "__main__": 20 | parser = argparse.ArgumentParser(description="Bulk accept offers in namebase") 21 | parser.add_argument("--token", required=True, help="Token for authentication") 22 | parser.add_argument("--threshold", required=True, help="Threshold for accepting offers in HNS (default 1000)") 23 | args = parser.parse_args() 24 | 25 | token = args.token 26 | threshold = float(args.threshold) 27 | 28 | headers = { 29 | "authority": "www.namebase.io", 30 | "accept": "application/json", 31 | "content-type": "application/json", 32 | "cookie": f"namebase-main={token}" 33 | } 34 | 35 | pending = get_pending(token) 36 | if pending['success'] != True: 37 | print("Error getting pending offers") 38 | exit(1) 39 | 40 | print("Checking offers: " + str(pending['totalCount'])) 41 | 42 | total = pending['totalCount'] 43 | seen = 0 44 | while seen < total: 45 | for offer in pending['domains']: 46 | if float(offer['highestCurrentOffer']) >= threshold: 47 | # Get best non expired offer 48 | offers = requests.get(f"https://www.namebase.io/api/v0/offers/history?domainOwnerId={offer['domainOwnerId']}", headers=headers).json() 49 | bestValidBid = -1 50 | bestValidBidID = "" 51 | for o in offers['negotiations']: 52 | for bid in o['history']['bids']: 53 | if bid['isExpired']: 54 | continue 55 | amount = float(bid['amount']) 56 | if amount > bestValidBid: 57 | bestValidBid = amount 58 | bestValidBidID = bid['bidId'] 59 | 60 | if bestValidBid >= threshold: 61 | acceptResponse = requests.post('https://www.namebase.io/api/v0/offers/bid',json={'bidId':bestValidBidID},headers=headers) 62 | if acceptResponse.status_code == 200: 63 | print(f"Accepted {offer['domain']} for {bestValidBid}") 64 | 65 | 66 | seen += len(pending['domains']) 67 | if seen < total: 68 | pending = get_pending(token,seen) 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /wp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script is used to install WordPress on your Linux server. 3 | # It will install it in a docker container. 4 | # Then it will create an NGINX reverse proxy to the container. 5 | 6 | # USAGE: 7 | # ./wp.sh [domain] [port offset] 8 | # [domain] is the domain name you want to use for your WordPress site (e.g. docker.freeconcept) 9 | # [port offset] is the offset you want to use for the port numbers. 10 | # This is used if you want to run multiple instances of WordPress on the same server. (e.g. 0, 1, 2, 3, etc.) 11 | 12 | 13 | 14 | # Variables 15 | # Set the domain name 16 | 17 | if [ -z "$1" ] 18 | then 19 | echo "Please enter a domain name as the first argument." 20 | exit 1 21 | fi 22 | 23 | DOMAIN="$1" 24 | echo "Setting up on domain name: $DOMAIN" 25 | 26 | # Set port offset 27 | # This is used to offset the port numbers so you can run multiple instances of WordPress on the same server. 28 | if [ -z "$2" ] 29 | then 30 | PORT_OFFSET=0 31 | else 32 | PORT_OFFSET="$2" 33 | fi 34 | 35 | 36 | # Update the system 37 | sudo apt update && sudo apt upgrade -y 38 | 39 | # Install Docker 40 | sudo apt install apt-transport-https ca-certificates curl software-properties-common -y 41 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 42 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 43 | sudo apt update 44 | apt-cache policy docker-ce 45 | sudo apt install docker-ce -y 46 | sudo apt install docker-compose -y 47 | 48 | mkdir wordpress-$DOMAIN 49 | cd wordpress-$DOMAIN 50 | 51 | # Generate passwords 52 | MYSQL_ROOT_PASSWORD=$(openssl rand -base64 32) 53 | MYSQL_PASSWORD=$(openssl rand -base64 32) 54 | 55 | # Create port numbers 56 | WORDPRESS_PORT=$((8000 + $PORT_OFFSET)) 57 | 58 | # Create the docker config file 59 | echo """ 60 | version: \"3\" 61 | services: 62 | ${DOMAIN}db: 63 | image: mysql:5.7 64 | restart: always 65 | environment: 66 | MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD 67 | MYSQL_DATABASE: WordPressDatabase 68 | MYSQL_USER: WordPressUser 69 | MYSQL_PASSWORD: $MYSQL_PASSWORD 70 | wordpress: 71 | depends_on: 72 | - ${DOMAIN}db 73 | image: wordpress:latest 74 | restart: always 75 | ports: 76 | - \"${WORDPRESS_PORT}:80\" 77 | environment: 78 | WORDPRESS_DB_HOST: ${DOMAIN}db:3306 79 | WORDPRESS_DB_USER: WordPressUser 80 | WORDPRESS_DB_PASSWORD: $MYSQL_PASSWORD 81 | WORDPRESS_DB_NAME: WordPressDatabase 82 | volumes: 83 | [\"./:/var/www/html\"] 84 | volumes: 85 | mysql: {} 86 | """ > docker-compose.yml 87 | 88 | # Start the containers 89 | docker-compose up -d 90 | 91 | # Create the NGINX 92 | sudo apt install nginx -y 93 | 94 | URL="http://localhost:$WORDPRESS_PORT" 95 | 96 | # Setup NGINX config 97 | printf "server { 98 | listen 80; 99 | listen [::]:80; 100 | server_name $DOMAIN; 101 | proxy_ssl_server_name on; 102 | location / { 103 | proxy_set_header X-Real-IP \$remote_addr; 104 | proxy_set_header Host \$http_host; 105 | proxy_set_header X-Forwarded-Host \$http_host; 106 | proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 107 | proxy_set_header X-Forwarded-Proto \$scheme; 108 | 109 | proxy_pass $URL; 110 | } 111 | 112 | listen 443 ssl; 113 | ssl_certificate /etc/ssl/$DOMAIN.crt; 114 | ssl_certificate_key /etc/ssl/$DOMAIN.key; 115 | }" > /etc/nginx/sites-available/$DOMAIN 116 | sudo ln -s /etc/nginx/sites-available/$DOMAIN /etc/nginx/sites-enabled/$DOMAIN 117 | 118 | #generate ssl certificate 119 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 120 | -keyout cert.key -out cert.crt -extensions ext -config \ 121 | <(echo "[req]"; 122 | echo distinguished_name=req; 123 | echo "[ext]"; 124 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 125 | echo "extendedKeyUsage=serverAuth"; 126 | echo "basicConstraints=critical,CA:FALSE"; 127 | echo "subjectAltName=DNS:$DOMAIN,DNS:*.$DOMAIN"; 128 | ) -subj "/CN=*.$DOMAIN" 129 | 130 | # Print TLSA record and store in file in case of lost output 131 | echo "Add this TLSA Record to your DNS:" 132 | echo -n "3 1 1 " && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 133 | 134 | # Save TLSA to file 135 | echo "Add this TLSA Record to your DNS:" > tlsa.txt 136 | echo -n "3 1 1 " >> tlsa.txt 137 | echo -n "" && openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 >> tlsa.txt 138 | 139 | sudo mv cert.key /etc/ssl/$DOMAIN.key 140 | sudo mv cert.crt /etc/ssl/$DOMAIN.crt 141 | 142 | # Restart to apply config file 143 | sudo systemctl restart nginx 144 | 145 | cd .. -------------------------------------------------------------------------------- /varo/README.md: -------------------------------------------------------------------------------- 1 | # Varo-Clone 2 | [Backend](#backend) 3 | [Frontend](#frontend) 4 | [Add Varo Admin](#add-varo-admin) 5 | [Add a second nameserver](#replication) 6 | [Updating Varo](#updating) 7 | 8 | ## Before starting 9 | Prerequisites: 10 | + An ICANN domain name (eg. yourdomain.com) - Replace with this 11 | + A HNS domain name (eg. yourtld) - Replace with this 12 | + Tailscale Account (Free plan is fine) - https://tailscale.com/ 13 | + Stripe Account - https://stripe.com/ 14 | + At least 2 servers (1 for frontend and 1-2 for backend) - Get cheap servers from [Linode](https://l.woodburn.au/linode) 15 | 16 | 17 | 18 | ## Outline of the setup process: 19 | 1. Set up a VPN tunnel between the backend and frontend (or use a shared network) 20 | 1. You will need to setup a firewall to block all ports except 53, 22 for the backend and 80, 443 for the frontend 21 | 2. I recommend tailscale VPN as it is super easy to setup and free 22 | 3. Save the VPN IP address of Backend for use in step 3.1 23 | 2. Set up Backend 24 | 1. Run the backend script (#backend) 25 | 2. Save the LOCALPASS and APIPASS for use in step 3.1 26 | 3. Test by ssh into the frontend and `curl ` 27 | 3. Set up Frontend 28 | 1. Run the frontend script (#frontend) passing ICANN Domain, HNS Domain, LOCALPASS, APIPASS and VPN IP 29 | 2. Fill in the config file with your website name, SMTP settings and stripe credentials 30 | 3. Create an account and add yourself as an admin (#add-varo-admin) using the email you used to create the account 31 | 4. Optional set up a second nameserver 32 | 1. Run the first master replication script (#replication) on the backend server you used in step 2 33 | 2. Set up the vpn tunnel between on the second nameserver 34 | 3. SFTP the varo.sql and pdns.sql files to the second nameserver 35 | 4. In the same dirrectory as the sql files run the second master replication script (#replication) on the second nameserver using the vpn ip of the backend server and the output from step 4.1 36 | 5. Add records 37 | 1. Create an account on the frontend and add the HNS domain to it 38 | 2. Add these records to the blockchain records (replacing old records if they exist) 39 | 1. Add a GLUE4 record for the HNS domain (eg. `ns1.yourtld`) to point it to the Backend Server and one for the second nameserver if you have one (eg. `ns2.yourtld`) 40 | 2. Add the DS record as provided in the frontend 41 | 3. Add an A record to the HNS domain on the frontend dashboard with the IP of the frontend server 42 | 4. Add the TLSA record as provided in the frontend script (or use the [TLSA Script](https://github.com/nathanwoodburn/HNS-server#forgot-your-tlsa-record) to retrieve it) 43 | 44 | 45 | Setup the backend first and use the output to setup the frontend. 46 | 47 | ## Backend 48 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/varo/mutual.sh` 49 | `sudo chmod +x mutual.sh` 50 | `sudo ./mutual.sh ` 51 | 52 | ## Frontend 53 | The frontend needs to be sent this information: 54 | IP of the backend 55 | LOCALPASS (generated and saved in /var/www/html/mutual/etc/password.txt) 56 | APIPASS (generated and saved in /var/www/html/mutual/etc/password.txt) 57 | ICANN Domain (Must have A record pointing to the frontend before running script or it will fail) 58 | HNS Domain 59 | 60 | 61 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/varo/dash.sh` 62 | `sudo chmod +x dash.sh` 63 | `sudo ./dash.sh ` 64 | 65 | You need to edit the config file to add your websites name, SMTP settings (to send password resets) and [stripe api key](https://dashboard.stripe.com/apikeys) (!This is needed to get the site working). 66 | `nano /var/www/html/dashboard/etc/config.php` 67 | Don't edit the passwords as they are generated and used by multiple processes. 68 | 69 | ## Add Varo Admin 70 | 71 | Add an admin to your varo installation. 72 | This gives them access to the admin panel. 73 | Run this script on the backend server. 74 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/varo/admin.sh` 75 | `sudo chmod +x admin.sh` 76 | `sudo ./admin.sh ` 77 | 78 | # Replication 79 | 80 | ## Master 81 | If this is the first slave to add to the master, run the following commands. 82 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/sql/mbackup.sh` 83 | `sudo chmod +x mbackup.sh` 84 | `sudo ./mbackup.sh` 85 | 86 | If you have already setup a slave, then run the following commands. 87 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/sql/addbackup.sh` 88 | `sudo chmod +x addbackup.sh` 89 | `sudo ./addbackup.sh` 90 | 91 | ## Slave 92 | First you need these config from the master setup 93 | + Master IP (Internal VPN or shared network is most secure) 94 | + Generated Password 95 | + Log 96 | + Pos 97 | 98 | You also need the two database exports (varo.sql and pdns.sql) in the directory you are when running the script. 99 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/sql/sbackup.sh` 100 | `sudo chmod +x sbackup.sh` 101 | `sudo ./sbackup.sh ` 102 | 103 | You then need to install the PDNS (Nameserver software) on the slave server. 104 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/varo/backupns.sh` 105 | `sudo chmod +x backupns.sh` 106 | `sudo ./backupns.sh` 107 | 108 | 109 | 110 | # Updating 111 | 112 | To update the frontend run the following commands. 113 | 114 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/varo/update-dash.sh` 115 | `sudo chmod +x update-dash.sh` 116 | `sudo ./update-dash.sh` 117 | 118 | To update the backend run the following commands. 119 | 120 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/varo/update-mutual.sh` 121 | `sudo chmod +x update-mutual.sh` 122 | `sudo ./update-mutual.sh` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HNS-server 2 | + [NGINX HNS](#nginx-hns) 3 | + [NGINX ICANN](#nginx-icann) 4 | + [Redirect/Mirror Automation](#automation) 5 | + [Varo Clone](varo) 6 | + [MariaDB Replication (after Varo cloning)](sql) 7 | + [Email Server](email) 8 | + [DoH Server](doh.md) 9 | + [HSD Scripts](hsd) 10 | + [Varo Auth](varo-auth) 11 | 12 | 13 | # NGINX-HNS 14 | Run Installation scripts as below. 15 | Then add A record to point to your server and add the TLSA generated by the script to your DNS. 16 | Running these scripts without arguments will start the interactive mode which will ask you for each variable. 17 | Variables should be in this format (changing as needed) 18 | Domain: `woodburn` or for slds `nathan.woodburn` 19 | Location: `/var/www/woodburn` 20 | URL: `https://nathan.woodburn.au` or `https://nathan.woodburn.au/about` 21 | 22 | 23 | 24 | ## Standard HNS domain with HTML content (TLD and Wildcard SLD) 25 | This creates a website with TLD and SLD pointing to one directory. 26 | This installs nginx as well as setup HNS domains. 27 | Change directory into the directory containing your website files. 28 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/new` 29 | `sudo chmod +x new` 30 | `sudo ./new ` 31 | 32 | 33 | ## Standard HNS domain with HTML content (SLD only) 34 | Same as above without wildcard. 35 | This uses the prexisting SSL Cert. 36 | So add the same TLSA DNS record as the previously generated one. 37 | Change directory into the directory containing your website files. 38 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/addsld` 39 | `sudo chmod +x addsld` 40 | `sudo ./addsld ` #For example nathan.woodburn would be `sudo ./addsld woodburn nathan` 41 | 42 | ## Proxy HNS domain to ICANN site (TLD and Wildcard SLD) 43 | This will create a mirror of the ICANN site showing the Handshake domain in the url bar. 44 | 45 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/proxy` 46 | `sudo chmod +x proxy` 47 | `sudo ./proxy ` 48 | 49 | Example proxy *.3dprintingservice -> nathan3dprinting.au 50 | `sudo ./proxy 3dprintingservice https://nathan3dprinting.au` 51 | 52 | ## Proxy HNS domain to ICANN site (SLD or TLD only) 53 | This will create a mirror of the ICANN site showing the Handshake domain in the url bar. 54 | This will only proxy the provided SLD or TLD. 55 | Eg only proxy nathan.3dprintingservice 56 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/proxy-sld` 57 | `sudo chmod +x proxy-sld` 58 | `sudo ./proxy-sld nathan.3dprintingservice https://nathan3dprinting.au` 59 | 60 | ## Redirect HNS domain to ICANN site (TLD and Wildcard SLD) 61 | Replace proxy with redirect to do a redirect instead of a mirror (proxy). 62 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/redirect` 63 | `sudo chmod +x redirect` 64 | `sudo ./redirect ` 65 | 66 | 67 | 68 | ## Redirect HNS domain to ICANN site (SLD or TLD only) 69 | Replace proxy with redirect to do a redirect instead of a mirror (proxy). 70 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/redirect-sld` 71 | `sudo chmod +x redirect-sld` 72 | `sudo ./redirect-sld nathan.3dprintingservice https://nathan3dprinting.au` 73 | 74 | 75 | ## Forgot your TLSA record? 76 | This script will find the TLSA record for you. 77 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/tlsa` 78 | `sudo chmod +x tlsa` 79 | `./tlsa ` 80 | 81 | # NGINX-ICANN 82 | 83 | ## Standard ICANN domain with HTML content 84 | First add A record to point to your server so Letsencrypt can generate you SSL cert. 85 | Then run this script 86 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/newicann` 87 | `sudo chmod +x newicann` 88 | `sudo ./newicann ` 89 | 90 | ## Proxy ICANN domain to another ICANN site 91 | Eg proxy 3dprinting.woodburn.au -> nathan3dprinting.au 92 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/proxyicann` 93 | `sudo chmod +x proxyicann` 94 | `sudo ./proxyicann 3dprinting.woodburn.au https://nathan3dprinting.au` 95 | 96 | ## Redirect ICANN domain to another ICANN site 97 | Eg redirect 3dprinting.woodburn.au -> nathan3dprinting.au 98 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/redirecticann` 99 | `sudo chmod +x redirecticann` 100 | `sudo ./redirecticann 3dprinting.woodburn.au https://nathan3dprinting.au` 101 | 102 | 103 | # Wordpress 104 | ## Wordpress with HNS domain 105 | ```sh 106 | wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/wp.sh 107 | sudo chmod +x wp.sh 108 | sudo ./wp.sh 109 | ``` 110 | 111 | If you want to have multiple wordpress sites on the same server you can use the following command to create a new wordpress site. The port offset only affects the port used for the wordpress site. The port used for the HNS domain will always be 80 & 443. 112 | ```sh 113 | sudo ./wp.sh 114 | ``` 115 | 116 | # Static html site from Git repo 117 | ```sh 118 | wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/git.sh 119 | sudo chmod +x git.sh 120 | sudo ./git.sh 121 | ``` 122 | 123 | 124 | 125 | # Automation 126 | 127 | ## csv File Format 128 | The csv file should be format 129 | `,` 130 | Eg 131 | `3dprintingservice,https://nathan3dprinting.au` 132 | Please note you need a header row as this script will not use the first row of the csv file. 133 | 134 | ## Proxy HNS domain to ICANN site 135 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/proxy-csv.sh` 136 | `sudo chmod +x proxy-csv.sh` 137 | `sudo ./proxy-csv.sh ` 138 | 139 | ## Redirect HNS domain to ICANN site 140 | `wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/redirect-csv.sh` 141 | `sudo chmod +x redirect-csv.sh` 142 | `sudo ./redirect-csv.sh ` -------------------------------------------------------------------------------- /varo/mutual.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Do some checks 4 | 5 | # Check if root 6 | if [ "$EUID" -ne 0 ] 7 | then echo "Please run as root" 8 | exit 9 | fi 10 | 11 | # Check that 2 arguments are passed 12 | if [ "$#" -ne 2 ]; then 13 | echo "Illegal number of parameters" 14 | exit 15 | fi 16 | 17 | # Setup Nginx with php 18 | sudo apt-get update -y 19 | sudo apt-get install nginx git -y 20 | sudo apt-get install php8.1-fpm php-mysql -y 21 | 22 | #php-curl 23 | 24 | # Pull varodomains mutual 25 | cd /var/www/html 26 | git clone https://github.com/varodomains/mutual.git 27 | cd mutual 28 | 29 | # Add nginx server conf 30 | printf "server { 31 | listen 80; 32 | listen [::]:80; 33 | root /var/www/html/mutual; 34 | index index.php index.html index.htm; 35 | server_name default_server; 36 | location / { 37 | # First attempt to serve request as file, then 38 | # as directory, then fall back to displaying a 404. 39 | try_files \$uri \$uri/ =404; 40 | } 41 | # pass PHP scripts to FastCGI server 42 | location ~ \.php$ { 43 | include snippets/fastcgi-php.conf; 44 | fastcgi_pass unix:/run/php/php8.1-fpm.sock; 45 | } 46 | # deny access to .htaccess files, if Apache's document root 47 | # concurs with nginx's one 48 | location ~ /\.ht { 49 | deny all; 50 | } 51 | }" > /etc/nginx/sites-available/default 52 | 53 | # Restart nginx 54 | sudo systemctl restart nginx 55 | 56 | # Add read permission www dir 57 | sudo chmod 755 -R /var/www/html 58 | 59 | # Install mariadb 60 | sudo apt-get install mariadb-server -y 61 | 62 | # Generate random password 63 | LOCALPASS=$(date +%s | sha256sum | base64 | head -c 32) 64 | 65 | # Add local login 66 | sudo mysql -e "CREATE USER 'mutual'@'localhost' IDENTIFIED BY '$LOCALPASS';" 67 | 68 | # Create databases 69 | sudo mysql -e "CREATE DATABASE pdns;" 70 | sudo mysql -e "CREATE DATABASE varo;" 71 | 72 | # Add tables to pdns database 73 | sudo mysql pdns < /var/www/html/mutual/etc/tables.sql 74 | 75 | # Wget varo sql tables 76 | wget https://raw.githubusercontent.com/varodomains/dashboard/main/etc/tables.sql 77 | 78 | # Add tables to varo database 79 | sudo mysql varo < tables.sql 80 | rm tables.sql 81 | 82 | # Add perms 83 | sudo mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'mutual'@'localhost';" 84 | sudo mysql -e "FLUSH PRIVILEGES;" 85 | 86 | # Add remote user 87 | sudo mysql -e "CREATE USER 'mutual'@'%' IDENTIFIED BY '$LOCALPASS';" 88 | sudo mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'mutual'@'%';" 89 | sudo mysql -e "FLUSH PRIVILEGES;" 90 | 91 | # Open port to public 92 | oldbind="bind-address = 127.0.0.1" 93 | newbind="bind-address = 0.0.0.0" 94 | sudo sed -i "s/$oldbind/$newbind/g" /etc/mysql/mariadb.conf.d/50-server.cnf 95 | 96 | # Add replication settings (for later use) 97 | echo "server-id = 1" >> /etc/mysql/mariadb.conf.d/50-server.cnf 98 | echo "log_bin = /var/log/mysql/mysql-bin.log" >> /etc/mysql/mariadb.conf.d/50-server.cnf 99 | echo "log_bin_index =/var/log/mysql/mysql-bin.log.index" >> /etc/mysql/mariadb.conf.d/50-server.cnf 100 | echo "relay_log = /var/log/mysql/mysql-relay-bin" >> /etc/mysql/mariadb.conf.d/50-server.cnf 101 | echo "relay_log_index = /var/log/mysql/mysql-relay-bin.index" >> /etc/mysql/mariadb.conf.d/50-server.cnf 102 | 103 | sudo systemctl restart mariadb 104 | 105 | # Stop default DNS resolver 106 | systemctl disable --now systemd-resolved 107 | rm -rf /etc/resolv.conf 108 | echo "nameserver 1.1.1.1" > /etc/resolv.conf 109 | 110 | # Setup pdns with mariadb backend 111 | sudo apt-get install pdns-server pdns-backend-mysql -y 112 | 113 | # add gmysql config 114 | printf "launch+=gmysql 115 | gmysql-host=localhost 116 | gmysql-port=3306 117 | gmysql-user=mutual 118 | gmysql-password=$LOCALPASS 119 | gmysql-dbname=pdns 120 | gmysql-dnssec=yes" > /etc/powerdns/pdns.d/pdns.local.gmysql.conf 121 | 122 | # Make sure perms is correct 123 | chown pdns:pdns /etc/powerdns/pdns.d/pdns.local.gmysql.conf 124 | chmod 755 -R /etc/powerdns 125 | 126 | 127 | # Restart pdns 128 | systemctl restart pdns 129 | 130 | # Save password to file 131 | echo "LOCALPASS: $LOCALPASS" > /var/www/html/mutual/etc/password.txt 132 | echo "Password saved to /var/www/html/mutual/etc/password.txt" 133 | 134 | # Add cron 135 | crontab -l > cron 136 | printf "0 0 * * * /usr/bin/php /var/www/html/mutual/etc/tlds.php >/dev/null 2>&1 137 | " > cron 138 | crontab cron 139 | rm cron 140 | 141 | # Generate random password 142 | APIPASS=$(date +%s | sha256sum | base64 | head -c 32) 143 | 144 | # Create conf file 145 | printf "" > /var/www/html/mutual/etc/config.php 171 | 172 | # Save password to file 173 | echo "APIPASS: $APIPASS" >> /var/www/html/mutual/etc/password.txt 174 | 175 | # Add sudo to web user 176 | printf "User_Alias WEBAPI = www-data 177 | Cmnd_Alias PDNSUTIL = /usr/bin/pdnsutil 178 | WEBAPI ALL=NOPASSWD: PDNSUTIL 179 | " >> /etc/sudoers 180 | 181 | # Run tlds.php 182 | php /var/www/html/mutual/etc/tlds.php 183 | 184 | # Enable ALIAS records 185 | echo "resolver=1.1.1.1" >> /etc/powerdns/pdns.conf 186 | echo "expand-alias=yes" >> /etc/powerdns/pdns.conf 187 | systemctl restart pdns 188 | 189 | # Get public IP 190 | PUBIP=$(curl -s https://api.ipify.org) 191 | 192 | # Echo adding NS instructions 193 | echo "Add the following NS records to either the Blockchain records in Namebase or Bob/HSD:" 194 | echo "For Namebase:" 195 | echo "TYPE: NS" 196 | echo "NAME: ns1" 197 | echo "VALUE: $PUBIP" 198 | 199 | echo "For Bob/HSD:" 200 | echo "TYPE: GLUE4" 201 | echo "VALUE: ns1.$1. $PUBIP" 202 | 203 | echo "TYPE: NS" 204 | echo "VALUE: ns1.$1." 205 | echo "Note this isn't needed but might be used by some resolvers." -------------------------------------------------------------------------------- /varo/apache2.conf: -------------------------------------------------------------------------------- 1 | # This is the main Apache server configuration file. It contains the 2 | # configuration directives that give the server its instructions. 3 | # See http://httpd.apache.org/docs/2.4/ for detailed information about 4 | # the directives and /usr/share/doc/apache2/README.Debian about Debian specific 5 | # hints. 6 | # 7 | # 8 | # Summary of how the Apache 2 configuration works in Debian: 9 | # The Apache 2 web server configuration in Debian is quite different to 10 | # upstream's suggested way to configure the web server. This is because Debian's 11 | # default Apache2 installation attempts to make adding and removing modules, 12 | # virtual hosts, and extra configuration directives as flexible as possible, in 13 | # order to make automating the changes and administering the server as easy as 14 | # possible. 15 | 16 | # It is split into several files forming the configuration hierarchy outlined 17 | # below, all located in the /etc/apache2/ directory: 18 | # 19 | # /etc/apache2/ 20 | # |-- apache2.conf 21 | # | `-- ports.conf 22 | # |-- mods-enabled 23 | # | |-- *.load 24 | # | `-- *.conf 25 | # |-- conf-enabled 26 | # | `-- *.conf 27 | # `-- sites-enabled 28 | # `-- *.conf 29 | # 30 | # 31 | # * apache2.conf is the main configuration file (this file). It puts the pieces 32 | # together by including all remaining configuration files when starting up the 33 | # web server. 34 | # 35 | # * ports.conf is always included from the main configuration file. It is 36 | # supposed to determine listening ports for incoming connections which can be 37 | # customized anytime. 38 | # 39 | # * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ 40 | # directories contain particular configuration snippets which manage modules, 41 | # global configuration fragments, or virtual host configurations, 42 | # respectively. 43 | # 44 | # They are activated by symlinking available configuration files from their 45 | # respective *-available/ counterparts. These should be managed by using our 46 | # helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See 47 | # their respective man pages for detailed information. 48 | # 49 | # * The binary is called apache2. Due to the use of environment variables, in 50 | # the default configuration, apache2 needs to be started/stopped with 51 | # /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not 52 | # work with the default configuration. 53 | 54 | 55 | # Global configuration 56 | # 57 | 58 | # 59 | # ServerRoot: The top of the directory tree under which the server's 60 | # configuration, error, and log files are kept. 61 | # 62 | # NOTE! If you intend to place this on an NFS (or otherwise network) 63 | # mounted filesystem then please read the Mutex documentation (available 64 | # at ); 65 | # you will save yourself a lot of trouble. 66 | # 67 | # Do NOT add a slash at the end of the directory path. 68 | # 69 | #ServerRoot "/etc/apache2" 70 | 71 | # 72 | # The accept serialization lock file MUST BE STORED ON A LOCAL DISK. 73 | # 74 | #Mutex file:${APACHE_LOCK_DIR} default 75 | 76 | # 77 | # The directory where shm and other runtime files will be stored. 78 | # 79 | 80 | DefaultRuntimeDir ${APACHE_RUN_DIR} 81 | 82 | # 83 | # PidFile: The file in which the server should record its process 84 | # identification number when it starts. 85 | # This needs to be set in /etc/apache2/envvars 86 | # 87 | PidFile ${APACHE_PID_FILE} 88 | 89 | # 90 | # Timeout: The number of seconds before receives and sends time out. 91 | # 92 | Timeout 300 93 | 94 | # 95 | # KeepAlive: Whether or not to allow persistent connections (more than 96 | # one request per connection). Set to "Off" to deactivate. 97 | # 98 | KeepAlive On 99 | 100 | # 101 | # MaxKeepAliveRequests: The maximum number of requests to allow 102 | # during a persistent connection. Set to 0 to allow an unlimited amount. 103 | # We recommend you leave this number high, for maximum performance. 104 | # 105 | MaxKeepAliveRequests 100 106 | 107 | # 108 | # KeepAliveTimeout: Number of seconds to wait for the next request from the 109 | # same client on the same connection. 110 | # 111 | KeepAliveTimeout 5 112 | 113 | 114 | # These need to be set in /etc/apache2/envvars 115 | User ${APACHE_RUN_USER} 116 | Group ${APACHE_RUN_GROUP} 117 | 118 | # 119 | # HostnameLookups: Log the names of clients or just their IP addresses 120 | # e.g., www.apache.org (on) or 204.62.129.132 (off). 121 | # The default is off because it'd be overall better for the net if people 122 | # had to knowingly turn this feature on, since enabling it means that 123 | # each client request will result in AT LEAST one lookup request to the 124 | # nameserver. 125 | # 126 | HostnameLookups Off 127 | 128 | # ErrorLog: The location of the error log file. 129 | # If you do not specify an ErrorLog directive within a 130 | # container, error messages relating to that virtual host will be 131 | # logged here. If you *do* define an error logfile for a 132 | # container, that host's errors will be logged there and not here. 133 | # 134 | ErrorLog ${APACHE_LOG_DIR}/error.log 135 | 136 | # 137 | # LogLevel: Control the severity of messages logged to the error_log. 138 | # Available values: trace8, ..., trace1, debug, info, notice, warn, 139 | # error, crit, alert, emerg. 140 | # It is also possible to configure the log level for particular modules, e.g. 141 | # "LogLevel info ssl:warn" 142 | # 143 | LogLevel warn 144 | 145 | # Include module configuration: 146 | IncludeOptional mods-enabled/*.load 147 | IncludeOptional mods-enabled/*.conf 148 | 149 | # Include list of ports to listen on 150 | Include ports.conf 151 | 152 | 153 | # Sets the default security model of the Apache2 HTTPD server. It does 154 | # not allow access to the root filesystem outside of /usr/share and /var/www. 155 | # The former is used by web applications packaged in Debian, 156 | # the latter may be used for local directories served by the web server. If 157 | # your system is serving content from a sub-directory in /srv you must allow 158 | # access here, or in any related virtual host. 159 | 160 | Options FollowSymLinks 161 | AllowOverride None 162 | Require all denied 163 | 164 | 165 | 166 | AllowOverride None 167 | Require all granted 168 | 169 | 170 | 171 | Options +MultiViews +FollowSymLinks 172 | AllowOverride All 173 | Require all granted 174 | RewriteEngine on 175 | ReWriteCond %{SERVER_PORT} !^443$ 176 | RewriteRule ^/(.*) https://%{HTTP_HOST/$1 [NC,R,L] 177 | 178 | 179 | # 180 | # Options Indexes FollowSymLinks 181 | # AllowOverride None 182 | # Require all granted 183 | # 184 | 185 | 186 | 187 | 188 | # AccessFileName: The name of the file to look for in each directory 189 | # for additional configuration directives. See also the AllowOverride 190 | # directive. 191 | # 192 | AccessFileName .htaccess 193 | 194 | # 195 | # The following lines prevent .htaccess and .htpasswd files from being 196 | # viewed by Web clients. 197 | # 198 | 199 | Require all denied 200 | 201 | 202 | 203 | # 204 | # The following directives define some format nicknames for use with 205 | # a CustomLog directive. 206 | # 207 | # These deviate from the Common Log Format definitions in that they use %O 208 | # (the actual bytes sent including headers) instead of %b (the size of the 209 | # requested file), because the latter makes it impossible to detect partial 210 | # requests. 211 | # 212 | # Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. 213 | # Use mod_remoteip instead. 214 | # 215 | LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined 216 | LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined 217 | LogFormat "%h %l %u %t \"%r\" %>s %O" common 218 | LogFormat "%{Referer}i -> %U" referer 219 | LogFormat "%{User-agent}i" agent 220 | 221 | # Include of directories ignores editors' and dpkg's backup files, 222 | # see README.Debian for details. 223 | 224 | # Include generic snippets of statements 225 | IncludeOptional conf-enabled/*.conf 226 | 227 | # Include the virtual host configurations: 228 | IncludeOptional sites-enabled/*.conf 229 | 230 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 231 | AddType text/html php 232 | AddType application/x-httpd-php .php 233 | -------------------------------------------------------------------------------- /varo/dash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Do some checks 4 | 5 | # Check if root 6 | if [ "$EUID" -ne 0 ] 7 | then echo "Please run as root" 8 | exit 9 | fi 10 | 11 | # Check that 5 arguments are passed 12 | if [ "$#" -ne 5 ]; then 13 | echo "Illegal number of parameters" 14 | exit 15 | fi 16 | 17 | # Set variable 18 | ICANN=$1 19 | HANDSHAKE=$2 20 | LOCALPASS=$3 21 | APIPASS=$4 22 | IP=$5 23 | 24 | echo "Checking for ICANN DNS" 25 | # Check that ICANN DNS is set 26 | # Get public IP 27 | PUBLICIP=$(curl -s https://api.ipify.org) 28 | 29 | # Dig Cloudflare for ICANN DNS 30 | ICANNIP=$(dig +short $ICANN @1.1.1.1 | head -n 1) 31 | 32 | # Check if IP matches 33 | if [ "$PUBLICIP" != "$ICANNIP" ]; then 34 | echo "ICANN DNS is not set to $PUBLICIP" 35 | exit 36 | fi 37 | 38 | 39 | echo "Checks passed." 40 | echo "Installing a ton of stuff" 41 | # Update repo 42 | sudo apt-get update -y 43 | 44 | # Install a ton of things 45 | sudo apt-get install apache2 php php-mysql certbot python3-certbot-apache php-curl php-intl composer npm git -y 46 | sudo a2enmod rewrite ssl headers 47 | 48 | 49 | # Generate textonly password 50 | HSDAPI=$(date +%s | sha256sum | base64 | head -c 32) 51 | echo $HSDAPI > hsdapikey.txt 52 | 53 | echo "Pulling git repo" 54 | 55 | # Pull varodomains dashboard 56 | cd /var/www/html 57 | git clone https://github.com/varodomains/dashboard 58 | cd dashboard 59 | 60 | echo "Building from git repo" 61 | 62 | export COMPOSER_ALLOW_SUPERUSER=1; 63 | cd etc 64 | composer install 65 | cd .. 66 | 67 | echo "Generating Handshake SSL" 68 | 69 | # Generate SSL cert 70 | openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ 71 | -keyout cert.key -out cert.crt -extensions ext -config \ 72 | <(echo "[req]"; 73 | echo distinguished_name=req; 74 | echo "[ext]"; 75 | echo "keyUsage=critical,digitalSignature,keyEncipherment"; 76 | echo "extendedKeyUsage=serverAuth"; 77 | echo "basicConstraints=critical,CA:FALSE"; 78 | echo "subjectAltName=DNS:$HANDSHAKE,DNS:*.$HANDSHAKE"; 79 | ) -subj "/CN=*.$HANDSHAKE" 80 | 81 | mv cert.crt /etc/ssl/$HANDSHAKE.crt 82 | mv cert.key /etc/ssl/$HANDSHAKE.key 83 | 84 | # Give read permission to www dir 85 | sudo chmod 755 -R /var/www/html 86 | 87 | echo "Adding apache config" 88 | 89 | # Add apache2 server conf 90 | echo "" > /etc/apache2/sites-available/$HANDSHAKE.conf 91 | echo " ServerName $ICANN" >> /etc/apache2/sites-available/$HANDSHAKE.conf 92 | echo " ServerAdmin admin@$ICANN" >> /etc/apache2/sites-available/$HANDSHAKE.conf 93 | echo " DocumentRoot /var/www/html/dashboard" >> /etc/apache2/sites-available/$HANDSHAKE.conf 94 | echo " ErrorLog \${APACHE_LOG_DIR}/error.log" >> /etc/apache2/sites-available/$HANDSHAKE.conf 95 | echo " CustomLog \${APACHE_LOG_DIR}/access.log combined" >> /etc/apache2/sites-available/$HANDSHAKE.conf 96 | echo " Include conf-available/serve-cgi-bin.conf" >> /etc/apache2/sites-available/$HANDSHAKE.conf 97 | echo " DirectoryIndex index.html index.php" >> /etc/apache2/sites-available/$HANDSHAKE.conf 98 | echo " RewriteEngine on" >> /etc/apache2/sites-available/$HANDSHAKE.conf 99 | echo " RewriteCond %{SERVER_PORT} \!^443\$" >> /etc/apache2/sites-available/$HANDSHAKE.conf 100 | echo " RewriteRule ^/(.*) https://%{HTTP_HOST}/\$1 [NC,R,L]" >> /etc/apache2/sites-available/$HANDSHAKE.conf 101 | echo "" >> /etc/apache2/sites-available/$HANDSHAKE.conf 102 | echo "" >> /etc/apache2/sites-available/$HANDSHAKE.conf 103 | echo " ServerName $HANDSHAKE" >> /etc/apache2/sites-available/$HANDSHAKE.conf 104 | echo " DocumentRoot /var/www/html/dashboard" >> /etc/apache2/sites-available/$HANDSHAKE.conf 105 | echo "" >> /etc/apache2/sites-available/$HANDSHAKE.conf 106 | 107 | # Remove \ (for some reason it adds \ instead of escaping ! but without ! it causes errors) 108 | sed -i 's/\\//g' /etc/apache2/sites-available/$HANDSHAKE.conf 109 | 110 | # Enable site and restart apache 111 | echo "Applying apache config" 112 | sudo a2ensite $HANDSHAKE.conf 113 | systemctl restart apache2 114 | 115 | # Add LetsEncrypt cert 116 | echo "Adding LetsEncrypt cert" 117 | sudo certbot --apache -d $ICANN 118 | 119 | # Add Handshake SSL config 120 | echo "Adding Handshake SSL config" 121 | 122 | echo "" >> /etc/apache2/sites-available/$HANDSHAKE.conf 123 | echo " ServerName $HANDSHAKE" >> /etc/apache2/sites-available/$HANDSHAKE.conf 124 | echo " DocumentRoot /var/www/html/dashboard" >> /etc/apache2/sites-available/$HANDSHAKE.conf 125 | echo " SSLEngine on" >> /etc/apache2/sites-available/$HANDSHAKE.conf 126 | echo " SSLCertificateFile /etc/ssl/$HANDSHAKE.crt" >> /etc/apache2/sites-available/$HANDSHAKE.conf 127 | echo " SSLCertificateKeyFile /etc/ssl/$HANDSHAKE.key" >> /etc/apache2/sites-available/$HANDSHAKE.conf 128 | echo "" >> /etc/apache2/sites-available/$HANDSHAKE.conf 129 | systemctl restart apache2 130 | 131 | # Adding varo config and setting variables 132 | echo "Autofilling Varo config" 133 | 134 | printf "" > /var/www/html/dashboard/etc/config.php 192 | 193 | 194 | # Add cronjob 195 | echo "Creating cronjob" 196 | crontab -l > cron 197 | printf "*/1 * * * * /usr/bin/php /var/www/html/dashboard/etc/cron.php >/dev/null 2>&1 198 | " >> cron 199 | crontab cron 200 | rm cron 201 | 202 | # Fix apache conf 203 | echo "Editing apache conf" 204 | rm /etc/apache2/apache2.conf 205 | wget https://raw.githubusercontent.com/Nathanwoodburn/HNS-server/main/varo/apache2.conf -O /etc/apache2/apache2.conf 206 | systemctl restart apache2 207 | sudo ufw allow 'Apache Full' 208 | 209 | 210 | # Install HSD 211 | echo "Installing HSD" 212 | cd etc 213 | git clone --depth 1 --branch latest https://github.com/handshake-org/hsd.git 214 | cd hsd 215 | npm install --omit=dev 216 | screen -dmS HSD ./bin/hsd --spv --api-key=$APIKEY 217 | echo "Started HSD use screen -r to view it" 218 | 219 | # Echo TLSA record 220 | echo "TLSA record:" 221 | echo -n "3 1 1 " && openssl x509 -in /etc/ssl/$HANDSHAKE.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32 222 | -------------------------------------------------------------------------------- /email/mailconfig.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/lib/mailinabox/env/bin/python 2 | 3 | # NOTE: 4 | # This script is run both using the system-wide Python 3 5 | # interpreter (/usr/bin/python3) as well as through the 6 | # virtualenv (/usr/local/lib/mailinabox/env). So only 7 | # import packages at the top level of this script that 8 | # are installed in *both* contexts. We use the system-wide 9 | # Python 3 in setup/questions.sh to validate the email 10 | # address entered by the user. 11 | 12 | import subprocess, shutil, os, sqlite3, re 13 | import utils 14 | from email_validator import validate_email as validate_email_, EmailNotValidError 15 | import idna 16 | 17 | def validate_email(email, mode=None): 18 | # Checks that an email address is syntactically valid. Returns True/False. 19 | # An email address may contain ASCII characters only because Dovecot's 20 | # authentication mechanism gets confused with other character encodings. 21 | # 22 | # When mode=="user", we're checking that this can be a user account name. 23 | # Dovecot has tighter restrictions - letters, numbers, underscore, and 24 | # dash only! 25 | # 26 | # When mode=="alias", we're allowing anything that can be in a Postfix 27 | # alias table, i.e. omitting the local part ("@domain.tld") is OK. 28 | 29 | # Check the syntax of the address. 30 | if re.search(r'[a-z0-9]*@[a-z]+(.[a-z]+)*',email): 31 | if mode == 'user': 32 | # There are a lot of characters permitted in email addresses, but 33 | # Dovecot's sqlite auth driver seems to get confused if there are any 34 | # unusual characters in the address. Bah. Also note that since 35 | # the mailbox path name is based on the email address, the address 36 | # shouldn't be absurdly long and must not have a forward slash. 37 | # Our database is case sensitive (oops), which affects mail delivery 38 | # (Postfix always queries in lowercase?), so also only permit lowercase 39 | # letters. 40 | if len(email) > 255: return False 41 | if re.search(r'[^\@\.a-z0-9_\-]+', email): 42 | return False 43 | 44 | # Everything looks good. 45 | return True 46 | else: 47 | return False 48 | 49 | 50 | 51 | def sanitize_idn_email_address(email): 52 | # The user may enter Unicode in an email address. Convert the domain part 53 | # to IDNA before going into our database. Leave the local part alone --- 54 | # although validate_email will reject non-ASCII characters. 55 | # 56 | # The domain name system only exists in ASCII, so it doesn't make sense 57 | # to store domain names in Unicode. We want to store what is meaningful 58 | # to the underlying protocols. 59 | try: 60 | localpart, domainpart = email.split("@") 61 | domainpart = idna.encode(domainpart).decode('ascii') 62 | return localpart + "@" + domainpart 63 | except (ValueError, idna.IDNAError): 64 | # ValueError: String does not have a single @-sign, so it is not 65 | # a valid email address. IDNAError: Domain part is not IDNA-valid. 66 | # Validation is not this function's job, so return value unchanged. 67 | # If there are non-ASCII characters it will be filtered out by 68 | # validate_email. 69 | return email 70 | 71 | def prettify_idn_email_address(email): 72 | # This is the opposite of sanitize_idn_email_address. We store domain 73 | # names in IDNA in the database, but we want to show Unicode to the user. 74 | try: 75 | localpart, domainpart = email.split("@") 76 | domainpart = idna.decode(domainpart.encode("ascii")) 77 | return localpart + "@" + domainpart 78 | except (ValueError, UnicodeError, idna.IDNAError): 79 | # Failed to decode IDNA, or the email address does not have a 80 | # single @-sign. Should never happen. 81 | return email 82 | 83 | def is_dcv_address(email): 84 | email = email.lower() 85 | for localpart in ("admin", "administrator", "postmaster", "hostmaster", "webmaster", "abuse"): 86 | if email.startswith(localpart+"@") or email.startswith(localpart+"+"): 87 | return True 88 | return False 89 | 90 | def open_database(env, with_connection=False): 91 | conn = sqlite3.connect(env["STORAGE_ROOT"] + "/mail/users.sqlite") 92 | if not with_connection: 93 | return conn.cursor() 94 | else: 95 | return conn, conn.cursor() 96 | 97 | def get_mail_users(env): 98 | # Returns a flat, sorted list of all user accounts. 99 | c = open_database(env) 100 | c.execute('SELECT email FROM users') 101 | users = [ row[0] for row in c.fetchall() ] 102 | return utils.sort_email_addresses(users, env) 103 | 104 | def get_mail_users_ex(env, with_archived=False): 105 | # Returns a complex data structure of all user accounts, optionally 106 | # including archived (status="inactive") accounts. 107 | # 108 | # [ 109 | # { 110 | # domain: "domain.tld", 111 | # users: [ 112 | # { 113 | # email: "name@domain.tld", 114 | # privileges: [ "priv1", "priv2", ... ], 115 | # status: "active" | "inactive", 116 | # }, 117 | # ... 118 | # ] 119 | # }, 120 | # ... 121 | # ] 122 | 123 | # Get users and their privileges. 124 | users = [] 125 | active_accounts = set() 126 | c = open_database(env) 127 | c.execute('SELECT email, privileges FROM users') 128 | for email, privileges in c.fetchall(): 129 | active_accounts.add(email) 130 | 131 | user = { 132 | "email": email, 133 | "privileges": parse_privs(privileges), 134 | "status": "active", 135 | } 136 | users.append(user) 137 | 138 | # Add in archived accounts. 139 | if with_archived: 140 | root = os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes') 141 | for domain in os.listdir(root): 142 | if os.path.isdir(os.path.join(root, domain)): 143 | for user in os.listdir(os.path.join(root, domain)): 144 | email = user + "@" + domain 145 | mbox = os.path.join(root, domain, user) 146 | if email in active_accounts: continue 147 | user = { 148 | "email": email, 149 | "privileges": [], 150 | "status": "inactive", 151 | "mailbox": mbox, 152 | } 153 | users.append(user) 154 | 155 | # Group by domain. 156 | domains = { } 157 | for user in users: 158 | domain = get_domain(user["email"]) 159 | if domain not in domains: 160 | domains[domain] = { 161 | "domain": domain, 162 | "users": [] 163 | } 164 | domains[domain]["users"].append(user) 165 | 166 | # Sort domains. 167 | domains = [domains[domain] for domain in utils.sort_domains(domains.keys(), env)] 168 | 169 | # Sort users within each domain first by status then lexicographically by email address. 170 | for domain in domains: 171 | domain["users"].sort(key = lambda user : (user["status"] != "active", user["email"])) 172 | 173 | return domains 174 | 175 | def get_admins(env): 176 | # Returns a set of users with admin privileges. 177 | users = set() 178 | for domain in get_mail_users_ex(env): 179 | for user in domain["users"]: 180 | if "admin" in user["privileges"]: 181 | users.add(user["email"]) 182 | return users 183 | 184 | def get_mail_aliases(env): 185 | # Returns a sorted list of tuples of (address, forward-tos, permitted-senders, auto). 186 | c = open_database(env) 187 | c.execute('SELECT source, destination, permitted_senders, 0 as auto FROM aliases UNION SELECT source, destination, permitted_senders, 1 as auto FROM auto_aliases') 188 | aliases = { row[0]: row for row in c.fetchall() } # make dict 189 | 190 | # put in a canonical order: sort by domain, then by email address lexicographically 191 | aliases = [ aliases[address] for address in utils.sort_email_addresses(aliases.keys(), env) ] 192 | return aliases 193 | 194 | def get_mail_aliases_ex(env): 195 | # Returns a complex data structure of all mail aliases, similar 196 | # to get_mail_users_ex. 197 | # 198 | # [ 199 | # { 200 | # domain: "domain.tld", 201 | # alias: [ 202 | # { 203 | # address: "name@domain.tld", # IDNA-encoded 204 | # address_display: "name@domain.tld", # full Unicode 205 | # forwards_to: ["user1@domain.com", "receiver-only1@domain.com", ...], 206 | # permitted_senders: ["user1@domain.com", "sender-only1@domain.com", ...] OR null, 207 | # auto: True|False 208 | # }, 209 | # ... 210 | # ] 211 | # }, 212 | # ... 213 | # ] 214 | 215 | domains = {} 216 | for address, forwards_to, permitted_senders, auto in get_mail_aliases(env): 217 | # skip auto domain maps since these are not informative in the control panel's aliases list 218 | if auto and address.startswith("@"): continue 219 | 220 | # get alias info 221 | domain = get_domain(address) 222 | 223 | # add to list 224 | if not domain in domains: 225 | domains[domain] = { 226 | "domain": domain, 227 | "aliases": [], 228 | } 229 | domains[domain]["aliases"].append({ 230 | "address": address, 231 | "address_display": prettify_idn_email_address(address), 232 | "forwards_to": [prettify_idn_email_address(r.strip()) for r in forwards_to.split(",")], 233 | "permitted_senders": [prettify_idn_email_address(s.strip()) for s in permitted_senders.split(",")] if permitted_senders is not None else None, 234 | "auto": bool(auto), 235 | }) 236 | 237 | # Sort domains. 238 | domains = [domains[domain] for domain in utils.sort_domains(domains.keys(), env)] 239 | 240 | # Sort aliases within each domain first by required-ness then lexicographically by address. 241 | for domain in domains: 242 | domain["aliases"].sort(key = lambda alias : (alias["auto"], alias["address"])) 243 | return domains 244 | 245 | def get_domain(emailaddr, as_unicode=True): 246 | # Gets the domain part of an email address. Turns IDNA 247 | # back to Unicode for display. 248 | ret = emailaddr.split('@', 1)[1] 249 | if as_unicode: 250 | try: 251 | ret = idna.decode(ret.encode('ascii')) 252 | except (ValueError, UnicodeError, idna.IDNAError): 253 | # Looks like we have an invalid email address in 254 | # the database. Now is not the time to complain. 255 | pass 256 | return ret 257 | 258 | def get_mail_domains(env, filter_aliases=lambda alias : True, users_only=False): 259 | # Returns the domain names (IDNA-encoded) of all of the email addresses 260 | # configured on the system. If users_only is True, only return domains 261 | # with email addresses that correspond to user accounts. Exclude Unicode 262 | # forms of domain names listed in the automatic aliases table. 263 | domains = [] 264 | domains.extend([get_domain(login, as_unicode=False) for login in get_mail_users(env)]) 265 | if not users_only: 266 | domains.extend([get_domain(address, as_unicode=False) for address, _, _, auto in get_mail_aliases(env) if filter_aliases(address) and not auto ]) 267 | return set(domains) 268 | 269 | def add_mail_user(email, pw, privs, env): 270 | # validate email 271 | if email.strip() == "": 272 | return ("No email address provided.", 400) 273 | elif not validate_email(email): 274 | return ("Invalid email address.", 400) 275 | elif not validate_email(email, mode='user'): 276 | return ("User account email addresses may only use the lowercase ASCII letters a-z, the digits 0-9, underscore (_), hyphen (-), and period (.).", 400) 277 | elif is_dcv_address(email) and len(get_mail_users(env)) > 0: 278 | # Make domain control validation hijacking a little harder to mess up by preventing the usual 279 | # addresses used for DCV from being user accounts. Except let it be the first account because 280 | # during box setup the user won't know the rules. 281 | return ("You may not make a user account for that address because it is frequently used for domain control validation. Use an alias instead if necessary.", 400) 282 | 283 | # validate password 284 | validate_password(pw) 285 | 286 | # validate privileges 287 | if privs is None or privs.strip() == "": 288 | privs = [] 289 | else: 290 | privs = privs.split("\n") 291 | for p in privs: 292 | validation = validate_privilege(p) 293 | if validation: return validation 294 | 295 | # get the database 296 | conn, c = open_database(env, with_connection=True) 297 | 298 | # hash the password 299 | pw = hash_password(pw) 300 | 301 | # add the user to the database 302 | try: 303 | c.execute("INSERT INTO users (email, password, privileges) VALUES (?, ?, ?)", 304 | (email, pw, "\n".join(privs))) 305 | except sqlite3.IntegrityError: 306 | return ("User already exists.", 400) 307 | 308 | # write databasebefore next step 309 | conn.commit() 310 | 311 | # Update things in case any new domains are added. 312 | return kick(env, "mail user added") 313 | 314 | def set_mail_password(email, pw, env): 315 | # validate that password is acceptable 316 | validate_password(pw) 317 | 318 | # hash the password 319 | pw = hash_password(pw) 320 | 321 | # update the database 322 | conn, c = open_database(env, with_connection=True) 323 | c.execute("UPDATE users SET password=? WHERE email=?", (pw, email)) 324 | if c.rowcount != 1: 325 | return ("That's not a user (%s)." % email, 400) 326 | conn.commit() 327 | return "OK" 328 | 329 | def hash_password(pw): 330 | # Turn the plain password into a Dovecot-format hashed password, meaning 331 | # something like "{SCHEME}hashedpassworddata". 332 | # http://wiki2.dovecot.org/Authentication/PasswordSchemes 333 | return utils.shell('check_output', ["/usr/bin/doveadm", "pw", "-s", "SHA512-CRYPT", "-p", pw]).strip() 334 | 335 | def get_mail_password(email, env): 336 | # Gets the hashed password for a user. Passwords are stored in Dovecot's 337 | # password format, with a prefixed scheme. 338 | # http://wiki2.dovecot.org/Authentication/PasswordSchemes 339 | # update the database 340 | c = open_database(env) 341 | c.execute('SELECT password FROM users WHERE email=?', (email,)) 342 | rows = c.fetchall() 343 | if len(rows) != 1: 344 | raise ValueError("That's not a user (%s)." % email) 345 | return rows[0][0] 346 | 347 | def remove_mail_user(email, env): 348 | # remove 349 | conn, c = open_database(env, with_connection=True) 350 | c.execute("DELETE FROM users WHERE email=?", (email,)) 351 | if c.rowcount != 1: 352 | return ("That's not a user (%s)." % email, 400) 353 | conn.commit() 354 | 355 | # Update things in case any domains are removed. 356 | return kick(env, "mail user removed") 357 | 358 | def parse_privs(value): 359 | return [p for p in value.split("\n") if p.strip() != ""] 360 | 361 | def get_mail_user_privileges(email, env, empty_on_error=False): 362 | # get privs 363 | c = open_database(env) 364 | c.execute('SELECT privileges FROM users WHERE email=?', (email,)) 365 | rows = c.fetchall() 366 | if len(rows) != 1: 367 | if empty_on_error: return [] 368 | return ("That's not a user (%s)." % email, 400) 369 | return parse_privs(rows[0][0]) 370 | 371 | def validate_privilege(priv): 372 | if "\n" in priv or priv.strip() == "": 373 | return ("That's not a valid privilege (%s)." % priv, 400) 374 | return None 375 | 376 | def add_remove_mail_user_privilege(email, priv, action, env): 377 | # validate 378 | validation = validate_privilege(priv) 379 | if validation: return validation 380 | 381 | # get existing privs, but may fail 382 | privs = get_mail_user_privileges(email, env) 383 | if isinstance(privs, tuple): return privs # error 384 | 385 | # update privs set 386 | if action == "add": 387 | if priv not in privs: 388 | privs.append(priv) 389 | elif action == "remove": 390 | privs = [p for p in privs if p != priv] 391 | else: 392 | return ("Invalid action.", 400) 393 | 394 | # commit to database 395 | conn, c = open_database(env, with_connection=True) 396 | c.execute("UPDATE users SET privileges=? WHERE email=?", ("\n".join(privs), email)) 397 | if c.rowcount != 1: 398 | return ("Something went wrong.", 400) 399 | conn.commit() 400 | 401 | return "OK" 402 | 403 | def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exists=False, do_kick=True): 404 | # convert Unicode domain to IDNA 405 | address = sanitize_idn_email_address(address) 406 | 407 | # Our database is case sensitive (oops), which affects mail delivery 408 | # (Postfix always queries in lowercase?), so force lowercase. 409 | address = address.lower() 410 | 411 | # validate address 412 | address = address.strip() 413 | if address == "": 414 | return ("No email address provided.", 400) 415 | if not validate_email(address, mode='alias'): 416 | return ("Invalid email address (%s)." % address, 400) 417 | 418 | # validate forwards_to 419 | validated_forwards_to = [] 420 | forwards_to = forwards_to.strip() 421 | 422 | # extra checks for email addresses used in domain control validation 423 | is_dcv_source = is_dcv_address(address) 424 | 425 | # Postfix allows a single @domain.tld as the destination, which means 426 | # the local part on the address is preserved in the rewrite. We must 427 | # try to convert Unicode to IDNA first before validating that it's a 428 | # legitimate alias address. Don't allow this sort of rewriting for 429 | # DCV source addresses. 430 | r1 = sanitize_idn_email_address(forwards_to) 431 | if validate_email(r1, mode='alias') and not is_dcv_source: 432 | validated_forwards_to.append(r1) 433 | 434 | else: 435 | # Parse comma and \n-separated destination emails & validate. In this 436 | # case, the forwards_to must be complete email addresses. 437 | for line in forwards_to.split("\n"): 438 | for email in line.split(","): 439 | email = email.strip() 440 | if email == "": continue 441 | email = sanitize_idn_email_address(email) # Unicode => IDNA 442 | # Strip any +tag from email alias and check privileges 443 | privileged_email = re.sub(r"(?=\+)[^@]*(?=@)",'',email) 444 | if not validate_email(email): 445 | return ("Invalid receiver email address (%s)." % email, 400) 446 | if is_dcv_source and not is_dcv_address(email) and "admin" not in get_mail_user_privileges(privileged_email, env, empty_on_error=True): 447 | # Make domain control validation hijacking a little harder to mess up by 448 | # requiring aliases for email addresses typically used in DCV to forward 449 | # only to accounts that are administrators on this system. 450 | return ("This alias can only have administrators of this system as destinations because the address is frequently used for domain control validation.", 400) 451 | validated_forwards_to.append(email) 452 | 453 | # validate permitted_senders 454 | valid_logins = get_mail_users(env) 455 | validated_permitted_senders = [] 456 | permitted_senders = permitted_senders.strip() 457 | 458 | # Parse comma and \n-separated sender logins & validate. The permitted_senders must be 459 | # valid usernames. 460 | for line in permitted_senders.split("\n"): 461 | for login in line.split(","): 462 | login = login.strip() 463 | if login == "": continue 464 | if login not in valid_logins: 465 | return ("Invalid permitted sender: %s is not a user on this system." % login, 400) 466 | validated_permitted_senders.append(login) 467 | 468 | # Make sure the alias has either a forwards_to or a permitted_sender. 469 | if len(validated_forwards_to) + len(validated_permitted_senders) == 0: 470 | return ("The alias must either forward to an address or have a permitted sender.", 400) 471 | 472 | # save to db 473 | 474 | forwards_to = ",".join(validated_forwards_to) 475 | 476 | if len(validated_permitted_senders) == 0: 477 | permitted_senders = None 478 | else: 479 | permitted_senders = ",".join(validated_permitted_senders) 480 | 481 | conn, c = open_database(env, with_connection=True) 482 | try: 483 | c.execute("INSERT INTO aliases (source, destination, permitted_senders) VALUES (?, ?, ?)", (address, forwards_to, permitted_senders)) 484 | return_status = "alias added" 485 | except sqlite3.IntegrityError: 486 | if not update_if_exists: 487 | return ("Alias already exists (%s)." % address, 400) 488 | else: 489 | c.execute("UPDATE aliases SET destination = ?, permitted_senders = ? WHERE source = ?", (forwards_to, permitted_senders, address)) 490 | return_status = "alias updated" 491 | 492 | conn.commit() 493 | 494 | if do_kick: 495 | # Update things in case any new domains are added. 496 | return kick(env, return_status) 497 | 498 | def remove_mail_alias(address, env, do_kick=True): 499 | # convert Unicode domain to IDNA 500 | address = sanitize_idn_email_address(address) 501 | 502 | # remove 503 | conn, c = open_database(env, with_connection=True) 504 | c.execute("DELETE FROM aliases WHERE source=?", (address,)) 505 | if c.rowcount != 1: 506 | return ("That's not an alias (%s)." % address, 400) 507 | conn.commit() 508 | 509 | if do_kick: 510 | # Update things in case any domains are removed. 511 | return kick(env, "alias removed") 512 | 513 | def add_auto_aliases(aliases, env): 514 | conn, c = open_database(env, with_connection=True) 515 | c.execute("DELETE FROM auto_aliases"); 516 | for source, destination in aliases.items(): 517 | c.execute("INSERT INTO auto_aliases (source, destination) VALUES (?, ?)", (source, destination)) 518 | conn.commit() 519 | 520 | def get_system_administrator(env): 521 | return "administrator@" + env['PRIMARY_HOSTNAME'] 522 | 523 | def get_required_aliases(env): 524 | # These are the aliases that must exist. 525 | aliases = set() 526 | 527 | # The system administrator alias is required. 528 | aliases.add(get_system_administrator(env)) 529 | 530 | # The hostmaster alias is exposed in the DNS SOA for each zone. 531 | aliases.add("hostmaster@" + env['PRIMARY_HOSTNAME']) 532 | 533 | # Get a list of domains we serve mail for, except ones for which the only 534 | # email on that domain are the required aliases or a catch-all/domain-forwarder. 535 | real_mail_domains = get_mail_domains(env, 536 | filter_aliases = lambda alias : 537 | not alias.startswith("postmaster@") 538 | and not alias.startswith("admin@") 539 | and not alias.startswith("abuse@") 540 | and not alias.startswith("@") 541 | ) 542 | 543 | # Create postmaster@, admin@ and abuse@ for all domains we serve 544 | # mail on. postmaster@ is assumed to exist by our Postfix configuration. 545 | # admin@isn't anything, but it might save the user some trouble e.g. when 546 | # buying an SSL certificate. 547 | # abuse@ is part of RFC2142: https://www.ietf.org/rfc/rfc2142.txt 548 | for domain in real_mail_domains: 549 | aliases.add("postmaster@" + domain) 550 | aliases.add("admin@" + domain) 551 | aliases.add("abuse@" + domain) 552 | 553 | return aliases 554 | 555 | def kick(env, mail_result=None): 556 | results = [] 557 | 558 | # Include the current operation's result in output. 559 | 560 | if mail_result is not None: 561 | results.append(mail_result + "\n") 562 | 563 | auto_aliases = { } 564 | 565 | # Mape required aliases to the administrator alias (which should be created manually). 566 | administrator = get_system_administrator(env) 567 | required_aliases = get_required_aliases(env) 568 | for alias in required_aliases: 569 | if alias == administrator: continue # don't make an alias from the administrator to itself --- this alias must be created manually 570 | auto_aliases[alias] = administrator 571 | 572 | # Add domain maps from Unicode forms of IDNA domains to the ASCII forms stored in the alias table. 573 | for domain in get_mail_domains(env): 574 | try: 575 | domain_unicode = idna.decode(domain.encode("ascii")) 576 | if domain == domain_unicode: continue # not an IDNA/Unicode domain 577 | auto_aliases["@" + domain_unicode] = "@" + domain 578 | except (ValueError, UnicodeError, idna.IDNAError): 579 | continue 580 | 581 | add_auto_aliases(auto_aliases, env) 582 | 583 | # Remove auto-generated postmaster/admin/abuse alises from the main aliases table. 584 | # They are now stored in the auto_aliases table. 585 | for address, forwards_to, permitted_senders, auto in get_mail_aliases(env): 586 | user, domain = address.split("@") 587 | if user in ("postmaster", "admin", "abuse") \ 588 | and address not in required_aliases \ 589 | and forwards_to == get_system_administrator(env) \ 590 | and not auto: 591 | remove_mail_alias(address, env, do_kick=False) 592 | results.append("removed alias %s (was to %s; domain no longer used for email)\n" % (address, forwards_to)) 593 | 594 | # Update DNS and nginx in case any domains are added/removed. 595 | 596 | from dns_update import do_dns_update 597 | results.append( do_dns_update(env) ) 598 | 599 | from web_update import do_web_update 600 | results.append( do_web_update(env) ) 601 | 602 | return "".join(s for s in results if s != "") 603 | 604 | def validate_password(pw): 605 | # validate password 606 | if pw.strip() == "": 607 | raise ValueError("No password provided.") 608 | if len(pw) < 8: 609 | raise ValueError("Passwords must be at least eight characters.") 610 | 611 | if __name__ == "__main__": 612 | import sys 613 | if len(sys.argv) > 2 and sys.argv[1] == "validate-email": 614 | # Validate that we can create a Dovecot account for a given string. 615 | if validate_email(sys.argv[2], mode='user'): 616 | sys.exit(0) 617 | else: 618 | sys.exit(1) 619 | 620 | if len(sys.argv) > 1 and sys.argv[1] == "update": 621 | from utils import load_environment 622 | print(kick(load_environment())) 623 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | --------------------------------------------------------------------------------