Browse Source

Initial release

master
Sebastian 1 year ago
commit
4ac9706860
Signed by: Sebastian Huebner <sebastian@hueb-ner.de> GPG Key ID: AFA10CBFE4391C0C
8 changed files with 412 additions and 0 deletions
  1. 5
    0
      LICENSE
  2. 10
    0
      README.md
  3. 15
    0
      config.cfg.lua
  4. 116
    0
      html/index.html
  5. 11
    0
      html/static/css/base-min.css
  6. 7
    0
      html/static/css/grids-min.css
  7. 7
    0
      html/static/css/grids-responsive-min.css
  8. 241
    0
      wmia.lua

+ 5
- 0
LICENSE View File

@@ -0,0 +1,5 @@
MIT License
Copyright (c) 2018 Sebastian Huebner
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 10
- 0
README.md View File

@@ -0,0 +1,10 @@
# whats-my-ip

A tiny "Whats my IP address" - service written in [Lua](https://lua.org/), using the [lua-http](https://github.com/daurnimator/lua-http) library.

## TODO

* Documentation (e.g. usage, installation)
* systemd file
* maybe reverse dns
* maybe geoip = http://geoip.nekudo.com

+ 15
- 0
config.cfg.lua View File

@@ -0,0 +1,15 @@
local config = {}

-- the host (ip or hostname) on which the service will listen (default: localhost)
config.host = 'localhost'

-- the port on which the service will listen (defaul: 9090)
config.port = 9090

-- the folder where static files are located (default: ./static/)
config.html_root = './html/'

-- the domain, which is shown on the html page (default: localhost)
config.domain = 'wmi.kokolor.es'

return config

+ 116
- 0
html/index.html View File

@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Whats my IP address?</title>
<meta name="description" content="Whats my IP address?">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="static/css/base-min.css">
<link rel="stylesheet" type="text/css" href="static/css/grids-min.css">
<link rel="stylesheet" type="text/css" href="static/css/grids-responsive-min.css">
<style>
.wmi-grid {
text-align: center;
}
.wmi-box {
margin: 20px;
}
.code-box {
text-align: left;
margin: 20px;
}
.ip {
border: 1px solid black;
border-radius: 15px;
font-size: 36px;
padding: 6px;
}
html, .pure-g [class *= "pure-u"] {
font-family: "Tahoma", "Geneva", sans-serif;
font-size: 18px;
}
body {
background-color: #f4b942;
margin-left: auto;
margin-right: auto;
max-width: 80%;
margin-bottom: 20px;
}
hr {
border-top: 1px solid black;
border-bottom: 1px solid black;
}
a:link { color: #000; }
a:visited { color: #000; }
</style>
</head>
<body>
<div class="wmi-grid">
<div class="pure-g">
<div class="pure-u-1">
<h2>hi!</h2>
<p>This is yet another whats-my-ip-address service. Soooo, here is your IP address:</p>
<p><code class="ip">{{ IP }}</code></p>
</div>
<div class="pure-u-1">
<h2>examples!</h2>
<p>Here you have some examples how you can use this service.</p>
</div>
<div class="pure-u-1 pure-u-md-1-2 pure-u-lg-1-3">
<div class="wmi-box">
<h3>JSON</h3>
<hr/>
<div class="code-box">
<pre>
$ curl -L {{ DOMAIN }}/json

}
"ip": {{ IP }}
}
</pre>
</div>
</div>
</div>
<div class="pure-u-1 pure-u-md-1-2 pure-u-lg-1-3">
<div class="wmi-box">
<h3>Plain</h3>
<hr/>
<div class="code-box">
<pre>
$ curl -L {{ DOMAIN }}

{{ IP }}
</pre>
</div>
</div>
</div>
<div class="pure-u-1 pure-u-md-1-2 pure-u-lg-1-3">
<div class="wmi-box">
<h3>More CLI examples</h3>
<hr/>
<div class="code-box">
<pre>
$ curl -L {{ DOMAIN }}
{{ IP }}

$ wget -qO- {{ DOMAIN }}
{{ IP }}

$ http --follow -b {{ DOMAIN }}
{{ IP }}
</pre>
</div>
</div>
</div>
<div class="pure-u-1">
<h2>credits!</h2>
<p>This little service is written in <a href="https://lua.org" target="_blank">Lua</a>, using the <a href="https://github.com/daurnimator/lua-http" target="_blank">lua-http library</a> and this
page is build with <a href="https://purecss.io/" target="_blank">Pure.CSS</a>. You can find the source files on my <a href="https://git.kokolor.es/imo/whats-my-ip-address" target="_blank">Gitea instance</a>,
<a href="https://teahub.io/imo/whats-my-ip-address" target="_blank">Teahub</a> or <a href="https://github.com/imolein/whats-my-ip-address" target="_blank">Github</a>.</p>
</div>
</div>
</div>
</body>
</html>

+ 11
- 0
html/static/css/base-min.css View File

@@ -0,0 +1,11 @@
/*!
Pure v1.0.0
Copyright 2013 Yahoo!
Licensed under the BSD License.
https://github.com/yahoo/pure/blob/master/LICENSE.md
*/
/*!
normalize.css v^3.0 | MIT License | git.io/normalize
Copyright (c) Nicolas Gallagher and Jonathan Neal
*/
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */img,legend{border:0}legend,td,th{padding:0}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}table{border-collapse:collapse;border-spacing:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}

+ 7
- 0
html/static/css/grids-min.css View File

@@ -0,0 +1,7 @@
/*!
Pure v1.0.0
Copyright 2013 Yahoo!
Licensed under the BSD License.
https://github.com/yahoo/pure/blob/master/LICENSE.md
*/
.pure-g{letter-spacing:-.31em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){table .pure-g{display:block}}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u,.pure-u-1,.pure-u-1-1,.pure-u-1-12,.pure-u-1-2,.pure-u-1-24,.pure-u-1-3,.pure-u-1-4,.pure-u-1-5,.pure-u-1-6,.pure-u-1-8,.pure-u-10-24,.pure-u-11-12,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-2-24,.pure-u-2-3,.pure-u-2-5,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24,.pure-u-3-24,.pure-u-3-4,.pure-u-3-5,.pure-u-3-8,.pure-u-4-24,.pure-u-4-5,.pure-u-5-12,.pure-u-5-24,.pure-u-5-5,.pure-u-5-6,.pure-u-5-8,.pure-u-6-24,.pure-u-7-12,.pure-u-7-24,.pure-u-7-8,.pure-u-8-24,.pure-u-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class*=pure-u]{font-family:sans-serif}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}

+ 7
- 0
html/static/css/grids-responsive-min.css
File diff suppressed because it is too large
View File


+ 241
- 0
wmia.lua View File

@@ -0,0 +1,241 @@
#!/usr/bin/env lua5.3
--- Whats my IP address
-- Yet another Whats my IP address - Service

local http_server = require('http.server')
local http_headers = require('http.headers')
local cerrno = require('cqueues.errno')
local lfs = require('lfs')
local mimetypes = require('mimetypes')


----------------------------------------------
--- Load configuration or use defaults ---
----------------------------------------------
local function loadconfig(cfg)
local conf, err = loadfile(cfg)
if not conf and err then
io.stdout:write(string.format('ERROR: Can\'t load config %s. Use defaults!\n', cfg))

return {
host = 'localhost',
port = 9090,
html_root = './html/',
domain = 'localhost'
}
end
return conf()
end

local CONFIG
if arg[1] and arg[1] == '-c' and arg[2] then
CONFIG = loadconfig(arg[2])
else
CONFIG = loadconfig('config.cfg.lua')
end
----------------------------------------------

----------------------------------------------
--- Helper functions ---
----------------------------------------------
-- takes linux system errors and return fitting
-- http status code
local function get_err_status_code(errnr)
if errnr == cerrno.ENOENT then
return '404'
elseif errnr == cerrno.EACCES then
return '403'
else
return '500'
end
end

-- return the ip address of the requester and
-- preffer the X-Real-IP over X-Forwared-For if
-- proxied
local function get_req_ip(con_ip, req_headers)
if con_ip == '127.0.0.1' and ( req_headers:has('x-real-ip') or req_headers:has('x-forwarded-for') ) then
-- preffer x-real-ip over x-forwarded-for
return req_headers:get('x-real-ip') or req_headers:get('x-forwarded-for')
end

return con_ip
end

-- opens and read the index.html, replaces the
-- placeholders and returns it
local function gen_index_site(ip)
local file, err, errnr = io.open(CONFIG.html_root .. 'index.html', 'r')
if not file and err then return nil, errnr end
local index = file:read('*a')
file:close()
return index:gsub('{{ IP }}', ip):gsub('{{ DOMAIN }}', CONFIG.domain)
end
----------------------------------------------

----------------------------------------------
--- Responder functions ---
----------------------------------------------
-- returns error pages
local function err_responder(stream, method, status, msg)
local res_headers = http_headers.new()
local http_state = {
['403'] = 'Forbidden',
['404'] = 'Not Found',
['500'] = 'Internatl Server Error'
}
msg = msg or http_state[status]
res_headers:append(':status', status)
res_headers:append('content_type', 'text/plain')
stream:write_headers(res_headers, method == 'HEAD')

if method ~= 'HEAD' then
assert(stream:write_body_from_string('Failed with: ' .. msg .. '\n'))
end
end

-- returns IP address in plain text
local function plaintxt_responder(stream, method, ip)
local res_headers = http_headers.new()
res_headers:append(':status', '200')
res_headers:append('content-type', 'text/plain')
stream:write_headers(res_headers, method == 'HEAD')
if method ~= 'HEAD' then
stream:write_body_from_string(ip)
end
end

-- returns fancy html page with IP address
local function html_responder(stream, method, ip)
local res_headers = http_headers.new()
local resp_content, errnr = gen_index_site(ip)
if resp_content then
res_headers:append(':status', '200')
res_headers:append('content-type', 'text/html')
stream:write_headers(res_headers, method == 'HEAD')
if method ~= 'HEAD' then
stream:write_body_from_string(resp_content)
end
else
err_responder(stream, method, get_err_status_code(errnr))
end
end

-- returns IP address in JSON
local function json_responder(stream, method, ip)
local res_headers = http_headers.new()
res_headers:append(':status', '200')
res_headers:append('content-type', 'application/json')
stream:write_headers(res_headers, method == 'HEAD')
if method ~= 'HEAD' then
stream:write_body_from_string(('{ "ip": %q }'):format(ip))
end
end
----------------------------------------------

----------------------------------------------
--- Serve static files ---
----------------------------------------------
-- serves static files, which are located under
-- $html_root/static
local function serve_static(stream, req_path, method)
local res_headers = http_headers.new()
local path = CONFIG.html_root .. req_path
local f_type = lfs.attributes(path, 'mode')
if f_type == 'file' then
local fd, err, errnr = io.open(path, 'rb')

if fd then
res_headers:append(':status', '200')
res_headers:append('content-type', mimetypes.guess(path))
assert(stream:write_headers(res_headers, method == 'HEAD'))

if method ~= 'HEAD' then
assert(stream:write_body_from_file(fd))
end
else
err_responder(stream, method, get_err_status_code(errnr), err)
end
else
err_responder(stream, method, '500')
end
end
----------------------------------------------

----------------------------------------------
--- Whats my IP address main handler ---
----------------------------------------------
local function wmia_handler(_, stream)
local cli_agents = { curl = true, wget = true, httpie = true }
local req_headers = assert(stream:get_headers()) -- get header object from stream
local req_method = req_headers:get(':method') -- get method from header object
local req_agent = req_headers:get('user-agent'):lower():match('^(%w+)/.*$')
local req_path = req_headers:get(':path'):lower() -- get path from, beginning from the last /
local ip

-- only GET and HEAD is allowed, response to others with status 403
if req_method == 'GET' or req_method == 'HEAD' then
ip = get_req_ip(select(2, stream:peername()), req_headers)

if req_path:match('^/$') then
if cli_agents[req_agent] then
plaintxt_responder(stream, req_method, ip)
else
html_responder(stream, req_method, ip)
end
elseif req_path:match('^/json$') then
json_responder(stream, req_method, ip)
elseif req_path:match('^/html$') then
html_responder(stream, req_method, ip)
elseif req_path:match('^/plain$') then
plaintxt_responder(stream, req_method, ip)
elseif req_path:match('^/static.*$') then
serve_static(stream, req_path:sub(2), req_method)
else
html_responder(stream, req_method, ip)
end
end
end

-- error handler, which writes error details to console
local function error_handler(_, context, op, err)
local msg = op .. " on " .. tostring(context) .. " failed"
if err then
msg = msg .. ": " .. tostring(err)
end
assert(io.stderr:write(msg, "\n"))
end
----------------------------------------------


----------------------------------------------
--- Start server ---
----------------------------------------------
local server = assert(http_server.listen({
host = CONFIG.host,
port = CONFIG.port,
onstream = wmia_handler,
onerror = error_handler
}))

assert(server:listen())

do
local _, host, port = server:localname()
io.stderr:write(string.format('Server is running on %s:%d\n', host, port))
end

assert(server:loop())
----------------------------------------------

Loading…
Cancel
Save