├── .env.development ├── .env.production ├── screenshot.png ├── babel.config.js ├── vue.config.js ├── .editorconfig ├── .gitignore ├── src ├── main.js ├── router │ └── index.js ├── App.vue ├── components │ ├── AboutPage.vue │ └── StartPage.vue └── assets │ └── css │ └── styles.css ├── public ├── index.html └── server │ └── index.php ├── README.md └── package.json /.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URL=http://localhost:9090/server/ 2 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URL=https://dnsbl.tebe.ch/server/ 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbreuss/dns-blacklist-check/HEAD/screenshot.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pages: { 3 | index: { 4 | entry: 'src/main.js', 5 | title: 'Are you Blacklisted?' 6 | } 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import 'bootstrap' 7 | 8 | Vue.config.productionTip = false 9 | 10 | new Vue({ 11 | router, 12 | render: h => h(App), 13 | }).$mount('#app') 14 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import AboutPage from '@/components/AboutPage' 4 | import StartPage from '@/components/StartPage' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'StartPage', 13 | component: StartPage 14 | }, 15 | { 16 | path: '/about', 17 | name: 'AboutPage', 18 | component: AboutPage 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DNS Blacklist Check 2 | 3 | DNSBL is a simple demo project using Vue.js and Server Sent Events (SSE). 4 | It checks if the given IP address or hostname is blacklisted on the configured dnsbl servers. 5 | 6 | ![Screenshot](screenshot.png) 7 | 8 | ## Prerequisites 9 | 10 | - PHP >= 5.6 11 | - Node & NMP 12 | 13 | ## Install 14 | 15 | ~~~bash 16 | git clone https://github.com/tbreuss/dns-blacklist-check.git 17 | cd dns-blacklist-check 18 | npm install 19 | ~~~ 20 | 21 | ## Start 22 | 23 | Start the internal PHP server with port 9090 and the client with port 8081. 24 | 25 | ~~~ 26 | npm run start 27 | ~~~ 28 | 29 | ## Demo 30 | 31 | Visit a demo website at 32 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 32 | 33 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ch.tebe.dnsbl", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "tbreuss ", 6 | "private": true, 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "lint": "vue-cli-service lint", 11 | "start": "php -S localhost:9090 -t public/server & npm run serve" 12 | }, 13 | "dependencies": { 14 | "bootstrap": "^4.0.0", 15 | "vue": "^2.6.11", 16 | "vue-router": "^3.0.1" 17 | }, 18 | "devDependencies": { 19 | "@vue/cli-plugin-babel": "~5.0.8", 20 | "@vue/cli-plugin-eslint": "~5.0.8", 21 | "@vue/cli-service": "~5.0.8", 22 | "babel-eslint": "^10.1.0", 23 | "eslint": "^6.7.2", 24 | "eslint-plugin-vue": "^6.2.2", 25 | "vue-template-compiler": "^2.6.11" 26 | }, 27 | "eslintConfig": { 28 | "root": true, 29 | "env": { 30 | "node": true 31 | }, 32 | "extends": [ 33 | "plugin:vue/essential", 34 | "eslint:recommended" 35 | ], 36 | "parserOptions": { 37 | "parser": "babel-eslint" 38 | }, 39 | "rules": {} 40 | }, 41 | "browserslist": [ 42 | "> 1%", 43 | "last 2 versions", 44 | "not dead" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /src/components/AboutPage.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 22 | 38 | -------------------------------------------------------------------------------- /src/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | /* Links */ 6 | a, 7 | a:focus, 8 | a:hover { 9 | color: #fff; 10 | } 11 | 12 | /* Custom default button */ 13 | .btn-secondary, 14 | .btn-secondary:hover, 15 | .btn-secondary:focus { 16 | color: #333; 17 | text-shadow: none; /* Prevent inheritance from `body` */ 18 | background-color: #fff; 19 | border: .05rem solid #fff; 20 | } 21 | 22 | 23 | /* 24 | * Base structure 25 | */ 26 | 27 | html, 28 | body { 29 | width: 100%; 30 | height: 100%; 31 | background-color: #333; 32 | } 33 | 34 | body { 35 | display: -ms-flexbox; 36 | display: -webkit-box; 37 | display: flex; 38 | -ms-flex-pack: center; 39 | -webkit-box-pack: center; 40 | justify-content: center; 41 | color: #fff; 42 | text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5); 43 | box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5); 44 | } 45 | 46 | .cover-container { 47 | max-width: 42em; 48 | } 49 | 50 | 51 | /* 52 | * Header 53 | */ 54 | .masthead { 55 | margin-bottom: 2rem; 56 | } 57 | 58 | .masthead-brand { 59 | margin-bottom: 0; 60 | } 61 | 62 | .nav-masthead .nav-link { 63 | padding: .25rem 0; 64 | font-weight: 700; 65 | color: rgba(255, 255, 255, .5); 66 | background-color: transparent; 67 | border-bottom: .25rem solid transparent; 68 | } 69 | 70 | .nav-masthead .nav-link:hover, 71 | .nav-masthead .nav-link:focus { 72 | border-bottom-color: rgba(255, 255, 255, .25); 73 | } 74 | 75 | .nav-masthead .nav-link + .nav-link { 76 | margin-left: 1rem; 77 | } 78 | 79 | .nav-masthead .active { 80 | color: #fff; 81 | border-bottom-color: #fff; 82 | } 83 | 84 | @media (min-width: 48em) { 85 | .masthead-brand { 86 | float: left; 87 | } 88 | .nav-masthead { 89 | float: right; 90 | } 91 | } 92 | 93 | 94 | /* 95 | * Cover 96 | */ 97 | .cover { 98 | padding: 0 1.5rem; 99 | } 100 | .cover .btn-lg { 101 | padding: .75rem 1.25rem; 102 | font-weight: 700; 103 | } 104 | 105 | 106 | /* 107 | * Footer 108 | */ 109 | .mastfoot { 110 | color: rgba(255, 255, 255, .5); 111 | } 112 | 113 | #app { 114 | width: 100%; 115 | height: 100%; 116 | overflow: auto; 117 | } 118 | 119 | footer a { 120 | color:rgba(255, 255, 255, .5); 121 | text-decoration: underline; 122 | } 123 | .cover { 124 | margin: 3rem 0; 125 | } 126 | .masthead-brand { 127 | float: none; 128 | } 129 | -------------------------------------------------------------------------------- /src/components/StartPage.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 87 | 88 | 89 | 128 | -------------------------------------------------------------------------------- /public/server/index.php: -------------------------------------------------------------------------------- 1 | ['default' => '']] 86 | ); 87 | 88 | $msg = ''; 89 | if ($ip === '') { 90 | $msg = 'No IP given'; 91 | } 92 | 93 | // Make sure output buffering is turned off 94 | @ob_end_flush(); 95 | 96 | // Header 97 | 98 | header('Access-Control-Allow-Origin: *'); 99 | header('Content-Type: text/event-stream; charset=UTF-8'); 100 | 101 | echo dataToStreamEvent 102 | ( 103 | 'header', 104 | ['totalItems' => $repeat, 'msg' => $msg] 105 | ); 106 | flush(); 107 | 108 | // Message times repeat 109 | 110 | 111 | $reverse_ip = implode(".", array_reverse(explode(".", $ip))); 112 | 113 | foreach ($dnsbl_lookup as $i => $host) { 114 | $dnsr = $reverse_ip . "." . $host . "."; 115 | $time_start = microtime(true); 116 | if (checkdnsrr($dnsr, "A")) { 117 | $listed = true; 118 | } else { 119 | $listed = false; 120 | } 121 | $time_end = microtime(true); 122 | $time = $time_end - $time_start; 123 | $time = ($time >= 1.0) ? round($time, 2) . 's' : round($time * 1000, 2) . 'ms'; 124 | 125 | echo dataToStreamEvent( 126 | 'item', 127 | [ 128 | 'cnt' => $i+1, 129 | 'host' => $host, 130 | 'listed' => $listed, 131 | 'time' => $time, 132 | ] 133 | ); 134 | flush(); 135 | } 136 | 137 | // Tell client to close the connection 138 | 139 | echo dataToStreamEvent('close', []); 140 | flush(); 141 | 142 | /** 143 | * @param string $type 144 | * @param array $data 145 | * @param string $id 146 | * @return string text/event-stream formatted string 147 | */ 148 | function dataToStreamEvent($type, array $data, $id = '') 149 | { 150 | $result = ''; 151 | 152 | if ($type !== '') { 153 | $result .= sprintf("event:%s\n", strtr($type, ["\r" => ' ', "\n" => ' '])); 154 | } 155 | 156 | if ($id !== '') { 157 | $result .= sprintf("id:%s\n", strtr($id, ["\r" => ' ', "\n" => ' '])); 158 | } 159 | 160 | $result .= "data:" . strtr(json_encode($data), ["\r\n" => "\n", "\r" => "\n", "\n" => "\ndata:"]); 161 | 162 | $result .= "\n\n"; 163 | 164 | return $result; 165 | } 166 | --------------------------------------------------------------------------------