From 97635546abb5f3f79e4a45b804848e6ae88d56a2 Mon Sep 17 00:00:00 2001 From: ubuntu <ubuntu@ubuntu.com> Date: Tue, 26 Jun 2018 11:28:43 +0800 Subject: [PATCH] MMP, Cancel fork, because i do not know how to merge to the original foreign author --- .gitignore | 1 + README.md | 109 ++ clients/client-linux.py | 240 ++++ clients/client-psutil.py | 209 ++++ server/.gitignore | 2 + server/Makefile | 34 + server/config.json | 37 + server/include/argparse.h | 139 +++ server/include/detect.h | 149 +++ server/include/json.h | 269 +++++ server/include/system.h | 1299 +++++++++++++++++++++ server/obj/.gitignore | 1 + server/src/argparse.c | 322 ++++++ server/src/json.c | 949 ++++++++++++++++ server/src/main.cpp | 468 ++++++++ server/src/main.h | 93 ++ server/src/netban.cpp | 463 ++++++++ server/src/netban.h | 179 +++ server/src/network.cpp | 144 +++ server/src/network.h | 87 ++ server/src/network_client.cpp | 184 +++ server/src/server.cpp | 204 ++++ server/src/server.h | 46 + server/src/system.c | 2001 +++++++++++++++++++++++++++++++++ web/css/dark.css | 49 + web/css/light.css | 46 + web/favicon.ico | Bin 0 -> 45470 bytes web/img/dark.png | Bin 0 -> 600 bytes web/img/light.png | Bin 0 -> 19452 bytes web/index.html | 112 ++ web/js/serverstatus.js | 393 +++++++ web/json/.gitignore | 2 + 32 files changed, 8231 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 clients/client-linux.py create mode 100755 clients/client-psutil.py create mode 100644 server/.gitignore create mode 100644 server/Makefile create mode 100644 server/config.json create mode 100644 server/include/argparse.h create mode 100644 server/include/detect.h create mode 100644 server/include/json.h create mode 100644 server/include/system.h create mode 100644 server/obj/.gitignore create mode 100644 server/src/argparse.c create mode 100644 server/src/json.c create mode 100644 server/src/main.cpp create mode 100644 server/src/main.h create mode 100644 server/src/netban.cpp create mode 100644 server/src/netban.h create mode 100644 server/src/network.cpp create mode 100644 server/src/network.h create mode 100644 server/src/network_client.cpp create mode 100644 server/src/server.cpp create mode 100644 server/src/server.h create mode 100644 server/src/system.c create mode 100644 web/css/dark.css create mode 100644 web/css/light.css create mode 100644 web/favicon.ico create mode 100644 web/img/dark.png create mode 100644 web/img/light.png create mode 100644 web/index.html create mode 100644 web/js/serverstatus.js create mode 100644 web/json/.gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd5a000 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +default.sublime-workspace \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b949fe --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# ServerStatus中文版: + +* ServerStatus中文版是一个酷炫高逼格的云探针、云监控、服务器云监控、多服务器探针~。 +* 在线演示:https://tz.cloudcpp.com + +# 目录介绍: + +* clients 客户端文件 +* server 服务端文件 +* web 网站文件 + +# 更新说明: + +* 20180314, 调整前端,置默认密码为,设置ip和user即可上线 +* 20180312, 加入失联(被照顾)检测【正常:MH361, 屏蔽:MH370】,校准虚拟化(container)流量统计异常 +* 20170807, 更新平均1,5,15负载 +* 20170607, 去掉无用的IPV6信息,增加服务器总流量监控 + +# 安装教程: + +【克隆代码】: +``` +git clone https://github.com/cppla/ServerStatus.git +``` + +【服务端配置】(服务端程序在ServerStatus/web下): + +一、生成服务端程序 +``` +cd ServerStatus/server +make +./sergate +``` +如果没错误提示,OK,ctrl+c关闭;如果有错误提示,检查35601端口是否被占用 + +二、修改配置文件 +修改config.json文件,注意username, password的值需要和客户端对应一致 +``` +{"servers": + [ + { + "username": "s01", + "name": "Mainserver 1", + "type": "Dedicated Server", + "host": "GenericServerHost123", + "location": "Austria", + "password": "some-hard-to-guess-copy-paste-password" + }, + ] +} +``` + +三、拷贝ServerStatus/status到你的网站目录 +例如: +``` +sudo cp -r ServerStatus/web/* /home/wwwroot/default +``` + +四、运行服务端: +web-dir参数为上一步设置的网站根目录,务必修改成自己网站的路径 +``` +./sergate --config=config.json --web-dir=/home/wwwroot/default +``` + +【客户端配置】(客户端程序在ServerStatus/clients下): +客户端有两个版本,client-linux为普通linux,client-psutil为跨平台版,普通版不成功,换成跨平台版即可。 + +一、client-linux版配置: +1、vim client-linux.py, 修改SERVER地址,username帐号, password密码 +2、python client-linux.py 运行即可。 + +二、client-psutil版配置: +1、安装psutil跨平台依赖库 +2、vim client-psutil.py, 修改SERVER地址,username帐号, password密码 +3、python client-psutil.py 运行即可。 +``` +### for Centos: +sudo yum -y install epel-release +sudo yum -y install python-pip +sudo yum clean all +sudo yum -y install gcc +sudo yum -y install python-devel +sudo pip install psutil +### for Ubuntu/Debian: +sudo root +apt-get -y install python-setuptools python-dev build-essential +apt-get -y install python-pip +pip install psutil +### for Windows: +打开网址:https://pypi.python.org/pypi?:action=display&name=psutil#downloads +下载psutil for windows程序包 +安装即可 +``` + +打开云探针页面,就可以正常的监控。接下来把服务器和客户端脚本自行加入开机启动,或者进程守护,或以后台方式运行即可!例如: nohup python client-linux.py & + +# 为什么会有ServerStatus中文版: + +* 有些功能确实没用 +* 原版本部署,英文说明复杂 +* 不符合中文版的习惯 +* 没有一次又一次的轮子,哪来如此优秀的云探针 + +# 相关开源项目,感谢: + +* ServerStatus:https://github.com/BotoX/ServerStatus +* mojeda: https://github.com/mojeda +* mojeda's ServerStatus: https://github.com/mojeda/ServerStatus +* BlueVM's project: http://www.lowendtalk.com/discussion/comment/169690#Comment_169690 diff --git a/clients/client-linux.py b/clients/client-linux.py new file mode 100755 index 0000000..7a810c0 --- /dev/null +++ b/clients/client-linux.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +# Update by : https://github.com/cppla/ServerStatus +# 支持Python版本:2.7 to 3.5 +# 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures +# 时间: 20180312 + + +SERVER = "127.0.0.1" +PORT = 35601 +USER = "s01" +PASSWORD = "USER_DEFAULT_PASSWORD" +INTERVAL = 1 #更新间隔 + + +import socket +import time +import string +import math +import re +import os +import json +import subprocess +import collections + +def get_uptime(): + f = open('/proc/uptime', 'r') + uptime = f.readline() + f.close() + uptime = uptime.split('.', 2) + time = int(uptime[0]) + return int(time) + +def get_memory(): + re_parser = re.compile(r'^(?P<key>\S*):\s*(?P<value>\d*)\s*kB') + result = dict() + for line in open('/proc/meminfo'): + match = re_parser.match(line) + if not match: + continue; + key, value = match.groups(['key', 'value']) + result[key] = int(value) + + MemTotal = float(result['MemTotal']) + MemFree = float(result['MemFree']) + Cached = float(result['Cached']) + MemUsed = MemTotal - (Cached + MemFree) + SwapTotal = float(result['SwapTotal']) + SwapFree = float(result['SwapFree']) + return int(MemTotal), int(MemUsed), int(SwapTotal), int(SwapFree) + +def get_hdd(): + p = subprocess.check_output(['df', '-Tlm', '--total', '-t', 'ext4', '-t', 'ext3', '-t', 'ext2', '-t', 'reiserfs', '-t', 'jfs', '-t', 'ntfs', '-t', 'fat32', '-t', 'btrfs', '-t', 'fuseblk', '-t', 'zfs', '-t', 'simfs', '-t', 'xfs']).decode("Utf-8") + total = p.splitlines()[-1] + used = total.split()[3] + size = total.split()[2] + return int(size), int(used) + +def get_time(): + stat_file = file("/proc/stat", "r") + time_list = stat_file.readline().split(' ')[2:6] + stat_file.close() + for i in range(len(time_list)) : + time_list[i] = int(time_list[i]) + return time_list +def delta_time(): + x = get_time() + time.sleep(INTERVAL) + y = get_time() + for i in range(len(x)): + y[i]-=x[i] + return y +def get_cpu(): + t = delta_time() + st = sum(t) + if st == 0: + st = 1 + result = 100-(t[len(t)-1]*100.00/st) + return round(result) + +class Traffic: + def __init__(self): + self.rx = collections.deque(maxlen=10) + self.tx = collections.deque(maxlen=10) + def get(self): + f = open('/proc/net/dev', 'r') + net_dev = f.readlines() + f.close() + avgrx = 0; avgtx = 0 + + for dev in net_dev[2:]: + dev = dev.split(':') + if dev[0].strip() == "lo" or dev[0].find("tun") > -1 \ + or dev[0].find("docker") > -1 or dev[0].find("veth") > -1 \ + or dev[0].find("br-") > -1: + continue + dev = dev[1].split() + avgrx += int(dev[0]) + avgtx += int(dev[8]) + + self.rx.append(avgrx) + self.tx.append(avgtx) + avgrx = 0; avgtx = 0 + + l = len(self.rx) + for x in range(l - 1): + avgrx += self.rx[x+1] - self.rx[x] + avgtx += self.tx[x+1] - self.tx[x] + + avgrx = int(avgrx / l / INTERVAL) + avgtx = int(avgtx / l / INTERVAL) + + return avgrx, avgtx + +def liuliang(): + NET_IN = 0 + NET_OUT = 0 + with open('/proc/net/dev') as f: + for line in f.readlines(): + netinfo = re.findall('([^\s]+):[\s]{0,}(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)', line) + if netinfo: + if netinfo[0][0] == 'lo' or 'tun' in netinfo[0][0] \ + or 'docker' in netinfo[0][0] or 'veth' in netinfo[0][0] \ + or 'br-' in netinfo[0][0] \ + or netinfo[0][1]=='0' or netinfo[0][9]=='0': + continue + else: + NET_IN += int(netinfo[0][1]) + NET_OUT += int(netinfo[0][9]) + return NET_IN, NET_OUT + +# todo: 不确定是否要用多线程or多进程: 效率? 资源? +def ip_status(): + object_check = ['www.10010.com', 'www.189.cn', 'www.10086.cn'] + ip_check = 0 + for i in object_check: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + try: + s.connect((i, 80)) + except: + ip_check += 1 + s.close() + del s + if ip_check >= 2: + return False + else: + return True + +def get_network(ip_version): + if(ip_version == 4): + HOST = "ipv4.google.com" + elif(ip_version == 6): + HOST = "ipv6.google.com" + try: + s = socket.create_connection((HOST, 80), 2) + return True + except: + pass + return False + +if __name__ == '__main__': + socket.setdefaulttimeout(30) + while 1: + try: + print("Connecting...") + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((SERVER, PORT)) + data = s.recv(1024) + if data.find("Authentication required") > -1: + s.send(USER + ':' + PASSWORD + '\n') + data = s.recv(1024) + if data.find("Authentication successful") < 0: + print(data) + raise socket.error + else: + print(data) + raise socket.error + + print(data) + data = s.recv(1024) + print(data) + + timer = 0 + check_ip = 0 + if data.find("IPv4") > -1: + check_ip = 6 + elif data.find("IPv6") > -1: + check_ip = 4 + else: + print(data) + raise socket.error + + traffic = Traffic() + traffic.get() + while 1: + CPU = get_cpu() + NetRx, NetTx = traffic.get() + NET_IN, NET_OUT = liuliang() + Uptime = get_uptime() + Load_1, Load_5, Load_15 = os.getloadavg() + MemoryTotal, MemoryUsed, SwapTotal, SwapFree = get_memory() + HDDTotal, HDDUsed = get_hdd() + IP_STATUS = ip_status() + + array = {} + if not timer: + array['online' + str(check_ip)] = get_network(check_ip) + timer = 10 + else: + timer -= 1*INTERVAL + + array['uptime'] = Uptime + array['load_1'] = Load_1 + array['load_5'] = Load_5 + array['load_15'] = Load_15 + array['memory_total'] = MemoryTotal + array['memory_used'] = MemoryUsed + array['swap_total'] = SwapTotal + array['swap_used'] = SwapTotal - SwapFree + array['hdd_total'] = HDDTotal + array['hdd_used'] = HDDUsed + array['cpu'] = CPU + array['network_rx'] = NetRx + array['network_tx'] = NetTx + array['network_in'] = NET_IN + array['network_out'] = NET_OUT + array['ip_status'] = IP_STATUS + + s.send("update " + json.dumps(array) + "\n") + except KeyboardInterrupt: + raise + except socket.error: + print("Disconnected...") + # keep on trying after a disconnect + s.close() + time.sleep(3) + except Exception as e: + print("Caught Exception:", e) + s.close() + time.sleep(3) diff --git a/clients/client-psutil.py b/clients/client-psutil.py new file mode 100755 index 0000000..6c501b2 --- /dev/null +++ b/clients/client-psutil.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +# Update by : https://github.com/cppla/ServerStatus +# 依赖于psutil跨平台库: +# 支持Python版本:2.6 to 3.5 (users of Python 2.4 and 2.5 may use 2.1.3 version) +# 支持操作系统: Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures +# 时间: 20180312 + +SERVER = "127.0.0.1" +PORT = 35601 +USER = "s01" +PASSWORD = "USER_DEFAULT_PASSWORD" +INTERVAL = 1 # 更新间隔 + + +import socket +import time +import string +import math +import os +import json +import collections +import psutil +import sys + +def get_uptime(): + return int(time.time() - psutil.boot_time()) + +def get_memory(): + Mem = psutil.virtual_memory() + try: + MemUsed = Mem.total - (Mem.cached + Mem.free) + except: + MemUsed = Mem.total - Mem.free + return int(Mem.total/1024.0), int(MemUsed/1024.0) + +def get_swap(): + Mem = psutil.swap_memory() + return int(Mem.total/1024.0), int(Mem.used/1024.0) + +def get_hdd(): + valid_fs = [ "ext4", "ext3", "ext2", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs" ] + disks = dict() + size = 0 + used = 0 + for disk in psutil.disk_partitions(): + if not disk.device in disks and disk.fstype.lower() in valid_fs: + disks[disk.device] = disk.mountpoint + for disk in disks.itervalues(): + usage = psutil.disk_usage(disk) + size += usage.total + used += usage.used + return int(size/1024.0/1024.0), int(used/1024.0/1024.0) + +def get_cpu(): + return psutil.cpu_percent(interval=INTERVAL) + +class Traffic: + def __init__(self): + self.rx = collections.deque(maxlen=10) + self.tx = collections.deque(maxlen=10) + def get(self): + avgrx = 0; avgtx = 0 + for name, stats in psutil.net_io_counters(pernic=True).iteritems(): + if name == "lo" or name.find("tun") > -1 \ + or name.find("docker") > -1 or name.find("veth") > -1 \ + or name.find("br-") > -1: + continue + avgrx += stats.bytes_recv + avgtx += stats.bytes_sent + + self.rx.append(avgrx) + self.tx.append(avgtx) + avgrx = 0; avgtx = 0 + + l = len(self.rx) + for x in range(l - 1): + avgrx += self.rx[x+1] - self.rx[x] + avgtx += self.tx[x+1] - self.tx[x] + + avgrx = int(avgrx / l / INTERVAL) + avgtx = int(avgtx / l / INTERVAL) + + return avgrx, avgtx + +def liuliang(): + NET_IN = 0 + NET_OUT = 0 + net = psutil.net_io_counters(pernic=True) + for k, v in net.items(): + if k == 'lo' or 'tun' in k \ + or 'br-' in k \ + or 'docker' in k or 'veth' in k: + continue + else: + NET_IN += v[1] + NET_OUT += v[0] + return NET_IN, NET_OUT + +# todo: 不确定是否要用多线程or多进程: 效率? 资源? +def ip_status(): + object_check = ['www.10010.com', 'www.189.cn', 'www.10086.cn'] + ip_check = 0 + for i in object_check: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + try: + s.connect((i, 80)) + except: + ip_check += 1 + s.close() + del s + if ip_check >= 2: + return False + else: + return True + +def get_network(ip_version): + if(ip_version == 4): + HOST = "ipv4.google.com" + elif(ip_version == 6): + HOST = "ipv6.google.com" + try: + s = socket.create_connection((HOST, 80), 2) + return True + except: + pass + return False + +if __name__ == '__main__': + socket.setdefaulttimeout(30) + while 1: + try: + print("Connecting...") + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((SERVER, PORT)) + data = s.recv(1024) + if data.find("Authentication required") > -1: + s.send(USER + ':' + PASSWORD + '\n') + data = s.recv(1024) + if data.find("Authentication successful") < 0: + print(data) + raise socket.error + else: + print(data) + raise socket.error + + print(data) + data = s.recv(1024) + print(data) + + timer = 0 + check_ip = 0 + if data.find("IPv4") > -1: + check_ip = 6 + elif data.find("IPv6") > -1: + check_ip = 4 + else: + print(data) + raise socket.error + + traffic = Traffic() + traffic.get() + while 1: + CPU = get_cpu() + NetRx, NetTx = traffic.get() + NET_IN, NET_OUT = liuliang() + Uptime = get_uptime() + Load_1, Load_5, Load_15 = os.getloadavg() if 'linux' in sys.platform else (0.0, 0.0, 0.0) + MemoryTotal, MemoryUsed = get_memory() + SwapTotal, SwapUsed = get_swap() + HDDTotal, HDDUsed = get_hdd() + IP_STATUS = ip_status() + + array = {} + if not timer: + array['online' + str(check_ip)] = get_network(check_ip) + timer = 10 + else: + timer -= 1*INTERVAL + + array['uptime'] = Uptime + array['load_1'] = Load_1 + array['load_5'] = Load_5 + array['load_15'] = Load_15 + array['memory_total'] = MemoryTotal + array['memory_used'] = MemoryUsed + array['swap_total'] = SwapTotal + array['swap_used'] = SwapUsed + array['hdd_total'] = HDDTotal + array['hdd_used'] = HDDUsed + array['cpu'] = CPU + array['network_rx'] = NetRx + array['network_tx'] = NetTx + array['network_in'] = NET_IN + array['network_out'] = NET_OUT + array['ip_status'] = IP_STATUS + + s.send("update " + json.dumps(array) + "\n") + except KeyboardInterrupt: + raise + except socket.error: + print("Disconnected...") + # keep on trying after a disconnect + s.close() + time.sleep(3) + except Exception as e: + print("Caught Exception:", e) + s.close() + time.sleep(3) diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..6d7633f --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,2 @@ +sergate +.tags* diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 0000000..3ca07ff --- /dev/null +++ b/server/Makefile @@ -0,0 +1,34 @@ +OUT = sergate + +#CC = clang +CC = gcc +CFLAGS = -Wall -O2 + +#CXX = clang++ +CXX = g++ +CXXFLAGS = -Wall -O2 + +ODIR = obj +SDIR = src +LIBS = -pthread -lm +INC = -Iinclude + +C_SRCS := $(wildcard $(SDIR)/*.c) +CXX_SRCS := $(wildcard $(SDIR)/*.cpp) +C_OBJS := $(patsubst $(SDIR)/%.c,$(ODIR)/%.o,$(C_SRCS)) +CXX_OBJS := $(patsubst $(SDIR)/%.cpp,$(ODIR)/%.o,$(CXX_SRCS)) +OBJS := $(C_OBJS) $(CXX_OBJS) + +$(ODIR)/%.o: $(SDIR)/%.c + $(CC) -c $(INC) $(CFLAGS) $< -o $@ + +$(ODIR)/%.o: $(SDIR)/%.cpp + $(CXX) -c $(INC) $(CXXFLAGS) $< -o $@ + +$(OUT): $(OBJS) + $(CXX) $(LIBS) $^ -o $(OUT) + +.PHONY: clean + +clean: + rm -f $(ODIR)/*.o $(OUT) diff --git a/server/config.json b/server/config.json new file mode 100644 index 0000000..d5e823b --- /dev/null +++ b/server/config.json @@ -0,0 +1,37 @@ +{"servers": + [ + { + "username": "s01", + "name": "node1", + "type": "xen", + "host": "host1", + "location": "cn", + "password": "USER_DEFAULT_PASSWORD" + }, + { + "username": "s02", + "name": "node2", + "type": "vmware", + "host": "host2", + "location": "jp", + "password": "USER_DEFAULT_PASSWORD" + }, + { + "disabled": true, + "username": "s03", + "name": "node3", + "type": "Nothing", + "host": "host3", + "location": "fr", + "password": "USER_DEFAULT_PASSWORD" + }, + { + "username": "s04", + "name": "node4", + "type": "kvm", + "host": "host4", + "location": "kr", + "password": "USER_DEFAULT_PASSWORD" + } + ] +} \ No newline at end of file diff --git a/server/include/argparse.h b/server/include/argparse.h new file mode 100644 index 0000000..4430f94 --- /dev/null +++ b/server/include/argparse.h @@ -0,0 +1,139 @@ +#ifndef ARGPARSE_H +#define ARGPARSE_H + +/** + * Command-line arguments parsing library. + * + * This module is inspired by parse-options.c (git) and python's argparse + * module. + * + * Arguments parsing is common task in cli program, but traditional `getopt` + * libraries are not easy to use. This library provides high-level arguments + * parsing solutions. + * + * The program defines what arguments it requires, and `argparse` will figure + * out how to parse those out of `argc` and `argv`, it also automatically + * generates help and usage messages and issues errors when users give the + * program invalid arguments. + * + * Reserved namespaces: + * argparse + * OPT + * Author: Yecheng Fu <cofyc.jackson@gmail.com> + */ + +#include <assert.h> +#include <math.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct argparse; +struct argparse_option; + +typedef int argparse_callback(struct argparse *this_, + const struct argparse_option *option); + +enum argparse_flag { + ARGPARSE_STOP_AT_NON_OPTION = 1, +}; + +enum argparse_option_type { + /* special */ + ARGPARSE_OPT_END, + /* options with no arguments */ + ARGPARSE_OPT_BOOLEAN, + ARGPARSE_OPT_BIT, + /* options with arguments (optional or required) */ + ARGPARSE_OPT_INTEGER, + ARGPARSE_OPT_STRING, +}; + +enum argparse_option_flags { + OPT_NONEG = 1, /* Negation disabled. */ +}; + +/* + * Argparse option struct. + * + * `type`: + * holds the type of the option, you must have an ARGPARSE_OPT_END last in your + * array. + * + * `short_name`: + * the character to use as a short option name, '\0' if none. + * + * `long_name`: + * the long option name, without the leading dash, NULL if none. + * + * `value`: + * stores pointer to the value to be filled. + * + * `help`: + * the short help message associated to what the option does. + * Must never be NULL (except for ARGPARSE_OPT_END). + * + * `callback`: + * function is called when corresponding argument is parsed. + * + * `data`: + * associated data. Callbacks can use it like they want. + * + * `flags`: + * option flags. + * + */ +struct argparse_option { + enum argparse_option_type type; + const char short_name; + const char *long_name; + void *value; + const char *help; + argparse_callback *callback; + intptr_t data; + int flags; +}; + +/* + * argpparse + */ +struct argparse { + // user supplied + const struct argparse_option *options; + const char *usage; + int flags; + // internal context + int argc; + const char **argv; + const char **out; + int cpidx; + const char *optvalue; // current option value +}; + +// builtin callbacks +int argparse_help_cb(struct argparse *this_, + const struct argparse_option *option); + +// builtin option macros +#define OPT_END() { ARGPARSE_OPT_END, 0 } +#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ } +#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ } +#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ } +#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ } +#define OPT_HELP() OPT_BOOLEAN('h', "help", 0, "Show this help message and exit", argparse_help_cb) + +int argparse_init(struct argparse *this_, struct argparse_option *options, + const char *usage, int flags); +int argparse_parse(struct argparse *this_, int argc, const char **argv); +void argparse_usage(struct argparse *this_); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/server/include/detect.h b/server/include/detect.h new file mode 100644 index 0000000..0e2ef86 --- /dev/null +++ b/server/include/detect.h @@ -0,0 +1,149 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef BASE_DETECT_H +#define BASE_DETECT_H + +/* + this file detected the family, platform and architecture + to compile for. +*/ + +/* platforms */ + +/* windows Family */ +#if defined(WIN64) || defined(_WIN64) + /* Hmm, is this IA64 or x86-64? */ + #define CONF_FAMILY_WINDOWS 1 + #define CONF_FAMILY_STRING "windows" + #define CONF_PLATFORM_WIN64 1 + #define CONF_PLATFORM_STRING "win64" +#elif defined(WIN32) || defined(_WIN32) || defined(__CYGWIN32__) || defined(__MINGW32__) + #define CONF_FAMILY_WINDOWS 1 + #define CONF_FAMILY_STRING "windows" + #define CONF_PLATFORM_WIN32 1 + #define CONF_PLATFORM_STRING "win32" +#endif + +/* unix family */ +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + #define CONF_FAMILY_UNIX 1 + #define CONF_FAMILY_STRING "unix" + #define CONF_PLATFORM_FREEBSD 1 + #define CONF_PLATFORM_STRING "freebsd" +#endif + +#if defined(__OpenBSD__) + #define CONF_FAMILY_UNIX 1 + #define CONF_FAMILY_STRING "unix" + #define CONF_PLATFORM_OPENBSD 1 + #define CONF_PLATFORM_STRING "openbsd" +#endif + +#if defined(__LINUX__) || defined(__linux__) + #define CONF_FAMILY_UNIX 1 + #define CONF_FAMILY_STRING "unix" + #define CONF_PLATFORM_LINUX 1 + #define CONF_PLATFORM_STRING "linux" +#endif + +#if defined(__GNU__) || defined(__gnu__) + #define CONF_FAMILY_UNIX 1 + #define CONF_FAMILY_STRING "unix" + #define CONF_PLATFORM_HURD 1 + #define CONF_PLATFORM_STRING "gnu" +#endif + +#if defined(MACOSX) || defined(__APPLE__) || defined(__DARWIN__) + #define CONF_FAMILY_UNIX 1 + #define CONF_FAMILY_STRING "unix" + #define CONF_PLATFORM_MACOSX 1 + #define CONF_PLATFORM_STRING "macosx" +#endif + +#if defined(__sun) + #define CONF_FAMILY_UNIX 1 + #define CONF_FAMILY_STRING "unix" + #define CONF_PLATFORM_SOLARIS 1 + #define CONF_PLATFORM_STRING "solaris" +#endif + +/* beos family */ +#if defined(__BeOS) || defined(__BEOS__) + #define CONF_FAMILY_BEOS 1 + #define CONF_FAMILY_STRING "beos" + #define CONF_PLATFORM_BEOS 1 + #define CONF_PLATFORM_STRING "beos" +#endif + + +/* use gcc endianness definitions when available */ +#if defined(__GNUC__) && !defined(__APPLE__) && !defined(__MINGW32__) && !defined(__sun) + #if defined(__FreeBSD__) || defined(__OpenBSD__) + #include <sys/endian.h> + #else + #include <endian.h> + #endif + + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define CONF_ARCH_ENDIAN_LITTLE 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define CONF_ARCH_ENDIAN_BIG 1 + #endif +#endif + + +/* architectures */ +#if defined(i386) || defined(__i386__) || defined(__x86__) || defined(CONF_PLATFORM_WIN32) + #define CONF_ARCH_IA32 1 + #define CONF_ARCH_STRING "ia32" + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_LITTLE 1 + #endif +#endif + +#if defined(__ia64__) || defined(_M_IA64) + #define CONF_ARCH_IA64 1 + #define CONF_ARCH_STRING "ia64" + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_LITTLE 1 + #endif +#endif + +#if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64) + #define CONF_ARCH_AMD64 1 + #define CONF_ARCH_STRING "amd64" + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_LITTLE 1 + #endif +#endif + +#if defined(__powerpc__) || defined(__ppc__) + #define CONF_ARCH_PPC 1 + #define CONF_ARCH_STRING "ppc" + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_BIG 1 + #endif +#endif + +#if defined(__sparc__) + #define CONF_ARCH_SPARC 1 + #define CONF_ARCH_STRING "sparc" + #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) + #define CONF_ARCH_ENDIAN_BIG 1 + #endif +#endif + + +#ifndef CONF_FAMILY_STRING +#define CONF_FAMILY_STRING "unknown" +#endif + +#ifndef CONF_PLATFORM_STRING +#define CONF_PLATFORM_STRING "unknown" +#endif + +#ifndef CONF_ARCH_STRING +#define CONF_ARCH_STRING "unknown" +#endif + +#endif diff --git a/server/include/json.h b/server/include/json.h new file mode 100644 index 0000000..ed1f175 --- /dev/null +++ b/server/include/json.h @@ -0,0 +1,269 @@ + +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. + * https://github.com/udp/json-parser + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _JSON_H +#define _JSON_H + +#ifndef json_char + #define json_char char +#endif + +#ifndef json_int_t + #ifndef _MSC_VER + #include <inttypes.h> + #define json_int_t int64_t + #else + #define json_int_t __int64 + #endif +#endif + +#include <stdlib.h> + +#ifdef __cplusplus + + #include <string.h> + + extern "C" + { + +#endif + +typedef struct +{ + unsigned long max_memory; + int settings; + + /* Custom allocator support (leave null to use malloc/free) + */ + + void * (* mem_alloc) (size_t, int zero, void * user_data); + void (* mem_free) (void *, void * user_data); + + void * user_data; /* will be passed to mem_alloc and mem_free */ + +} json_settings; + +#define json_enable_comments 0x01 + +typedef enum +{ + json_none, + json_object, + json_array, + json_integer, + json_double, + json_string, + json_boolean, + json_null + +} json_type; + +extern const struct _json_value json_value_none; + +typedef struct _json_value +{ + struct _json_value * parent; + + json_type type; + + union + { + int boolean; + json_int_t integer; + double dbl; + + struct + { + unsigned int length; + json_char * ptr; /* null terminated */ + + } string; + + struct + { + unsigned int length; + + struct + { + json_char * name; + unsigned int name_length; + + struct _json_value * value; + + } * values; + + #if defined(__cplusplus) && __cplusplus >= 201103L + decltype(values) begin () const + { return values; + } + decltype(values) end () const + { return values + length; + } + #endif + + } object; + + struct + { + unsigned int length; + struct _json_value ** values; + + #if defined(__cplusplus) && __cplusplus >= 201103L + decltype(values) begin () const + { return values; + } + decltype(values) end () const + { return values + length; + } + #endif + + } array; + + } u; + + union + { + struct _json_value * next_alloc; + void * object_mem; + + } _reserved; + + + /* Some C++ operator sugar */ + + #ifdef __cplusplus + + public: + + inline _json_value () + { memset (this, 0, sizeof (_json_value)); + } + + inline const struct _json_value &operator [] (int index) const + { + if (type != json_array || index < 0 + || ((unsigned int) index) >= u.array.length) + { + return json_value_none; + } + + return *u.array.values [index]; + } + + inline const struct _json_value &operator [] (const char * index) const + { + if (type != json_object) + return json_value_none; + + for (unsigned int i = 0; i < u.object.length; ++ i) + if (!strcmp (u.object.values [i].name, index)) + return *u.object.values [i].value; + + return json_value_none; + } + + inline operator const char * () const + { + switch (type) + { + case json_string: + return u.string.ptr; + + default: + return ""; + }; + } + + inline operator json_int_t () const + { + switch (type) + { + case json_integer: + return u.integer; + + case json_double: + return (json_int_t) u.dbl; + + default: + return 0; + }; + } + + inline operator bool () const + { + if (type != json_boolean) + return false; + + return u.boolean != 0; + } + + inline operator double () const + { + switch (type) + { + case json_integer: + return (double) u.integer; + + case json_double: + return u.dbl; + + default: + return 0; + }; + } + + #endif + +} json_value; + +json_value * json_parse (const json_char * json, + size_t length); + +#define json_error_max 128 +json_value * json_parse_ex (json_settings * settings, + const json_char * json, + size_t length, + char * error); + +void json_value_free (json_value *); + + +/* Not usually necessary, unless you used a custom mem_alloc and now want to + * use a custom mem_free. + */ +void json_value_free_ex (json_settings * settings, + json_value *); + + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif diff --git a/server/include/system.h b/server/include/system.h new file mode 100644 index 0000000..7ba0c0a --- /dev/null +++ b/server/include/system.h @@ -0,0 +1,1299 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ + +/* + Title: OS Abstraction +*/ + +#ifndef BASE_SYSTEM_H +#define BASE_SYSTEM_H + +#include "detect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Group: Debug */ +/* + Function: dbg_assert + Breaks into the debugger based on a test. + + Parameters: + test - Result of the test. + msg - Message that should be printed if the test fails. + + Remarks: + Does nothing in release version of the library. + + See Also: + <dbg_break> +*/ +void dbg_assert(int test, const char *msg); +#define dbg_assert(test,msg) dbg_assert_imp(__FILE__, __LINE__, test, msg) +void dbg_assert_imp(const char *filename, int line, int test, const char *msg); + + +#ifdef __clang_analyzer__ +#include <assert.h> +#undef dbg_assert +#define dbg_assert(test,msg) assert(test) +#endif + +/* + Function: dbg_break + Breaks into the debugger. + + Remarks: + Does nothing in release version of the library. + + See Also: + <dbg_assert> +*/ +void dbg_break(); + +/* + Function: dbg_msg + + Prints a debug message. + + Parameters: + sys - A string that describes what system the message belongs to + fmt - A printf styled format string. + + Remarks: + Does nothing in release version of the library. + + See Also: + <dbg_assert> +*/ +void dbg_msg(const char *sys, const char *fmt, ...); + +/* Group: Memory */ + +/* + Function: mem_alloc + Allocates memory. + + Parameters: + size - Size of the needed block. + alignment - Alignment for the block. + + Returns: + Returns a pointer to the newly allocated block. Returns a + null pointer if the memory couldn't be allocated. + + Remarks: + - Passing 0 to size will allocated the smallest amount possible + and return a unique pointer. + + See Also: + <mem_free> +*/ +void *mem_alloc_debug(const char *filename, int line, unsigned size, unsigned alignment); +#define mem_alloc(s,a) mem_alloc_debug(__FILE__, __LINE__, (s), (a)) + +/* + Function: mem_free + Frees a block allocated through <mem_alloc>. + + Remarks: + - In the debug version of the library the function will assert if + a non-valid block is passed, like a null pointer or a block that + isn't allocated. + + See Also: + <mem_alloc> +*/ +void mem_free(void *block); + +/* + Function: mem_copy + Copies a a memory block. + + Parameters: + dest - Destination. + source - Source to copy. + size - Size of the block to copy. + + Remarks: + - This functions DOES NOT handles cases where source and + destination is overlapping. + + See Also: + <mem_move> +*/ +void mem_copy(void *dest, const void *source, unsigned size); + +/* + Function: mem_move + Copies a a memory block + + Parameters: + dest - Destination + source - Source to copy + size - Size of the block to copy + + Remarks: + - This functions handles cases where source and destination + is overlapping + + See Also: + <mem_copy> +*/ +void mem_move(void *dest, const void *source, unsigned size); + +/* + Function: mem_zero + Sets a complete memory block to 0 + + Parameters: + block - Pointer to the block to zero out + size - Size of the block +*/ +void mem_zero(void *block, unsigned size); + +/* + Function: mem_comp + Compares two blocks of memory + + Parameters: + a - First block of data + b - Second block of data + size - Size of the data to compare + + Returns: + <0 - Block a is lesser then block b + 0 - Block a is equal to block b + >0 - Block a is greater then block b +*/ +int mem_comp(const void *a, const void *b, int size); + +/* + Function: mem_check + Validates the heap + Will trigger a assert if memory has failed. +*/ +int mem_check_imp(); +#define mem_check() dbg_assert_imp(__FILE__, __LINE__, mem_check_imp(), "Memory check failed") + +/* Group: File IO */ +enum { + IOFLAG_READ = 1, + IOFLAG_WRITE = 2, + IOFLAG_RANDOM = 4, + + IOSEEK_START = 0, + IOSEEK_CUR = 1, + IOSEEK_END = 2 +}; + +typedef struct IOINTERNAL *IOHANDLE; + +/* + Function: io_open + Opens a file. + + Parameters: + filename - File to open. + flags - A set of flags. IOFLAG_READ, IOFLAG_WRITE, IOFLAG_RANDOM. + + Returns: + Returns a handle to the file on success and 0 on failure. + +*/ +IOHANDLE io_open(const char *filename, int flags); + +/* + Function: io_read + Reads data into a buffer from a file. + + Parameters: + io - Handle to the file to read data from. + buffer - Pointer to the buffer that will recive the data. + size - Number of bytes to read from the file. + + Returns: + Number of bytes read. + +*/ +unsigned io_read(IOHANDLE io, void *buffer, unsigned size); + +/* + Function: io_skip + Skips data in a file. + + Parameters: + io - Handle to the file. + size - Number of bytes to skip. + + Returns: + Number of bytes skipped. +*/ +unsigned io_skip(IOHANDLE io, int size); + +/* + Function: io_write + Writes data from a buffer to file. + + Parameters: + io - Handle to the file. + buffer - Pointer to the data that should be written. + size - Number of bytes to write. + + Returns: + Number of bytes written. +*/ +unsigned io_write(IOHANDLE io, const void *buffer, unsigned size); + +/* + Function: io_write_newline + Writes newline to file. + + Parameters: + io - Handle to the file. + + Returns: + Number of bytes written. +*/ +unsigned io_write_newline(IOHANDLE io); + +/* + Function: io_seek + Seeks to a specified offset in the file. + + Parameters: + io - Handle to the file. + offset - Offset from pos to stop. + origin - Position to start searching from. + + Returns: + Returns 0 on success. +*/ +int io_seek(IOHANDLE io, int offset, int origin); + +/* + Function: io_tell + Gets the current position in the file. + + Parameters: + io - Handle to the file. + + Returns: + Returns the current position. -1L if an error occured. +*/ +long int io_tell(IOHANDLE io); + +/* + Function: io_length + Gets the total length of the file. Resetting cursor to the beginning + + Parameters: + io - Handle to the file. + + Returns: + Returns the total size. -1L if an error occured. +*/ +long int io_length(IOHANDLE io); + +/* + Function: io_close + Closes a file. + + Parameters: + io - Handle to the file. + + Returns: + Returns 0 on success. +*/ +int io_close(IOHANDLE io); + +/* + Function: io_flush + Empties all buffers and writes all pending data. + + Parameters: + io - Handle to the file. + + Returns: + Returns 0 on success. +*/ +int io_flush(IOHANDLE io); + + +/* + Function: io_stdin + Returns an <IOHANDLE> to the standard input. +*/ +IOHANDLE io_stdin(); + +/* + Function: io_stdout + Returns an <IOHANDLE> to the standard output. +*/ +IOHANDLE io_stdout(); + +/* + Function: io_stderr + Returns an <IOHANDLE> to the standard error. +*/ +IOHANDLE io_stderr(); + + +/* Group: Threads */ + +/* + Function: thread_sleep + Suspends the current thread for a given period. + + Parameters: + milliseconds - Number of milliseconds to sleep. +*/ +void thread_sleep(int milliseconds); + +/* + Function: thread_create + Creates a new thread. + + Parameters: + threadfunc - Entry point for the new thread. + user - Pointer to pass to the thread. + +*/ +void *thread_create(void (*threadfunc)(void *), void *user); + +/* + Function: thread_wait + Waits for a thread to be done or destroyed. + + Parameters: + thread - Thread to wait for. +*/ +void thread_wait(void *thread); + +/* + Function: thread_destroy + Destroys a thread. + + Parameters: + thread - Thread to destroy. +*/ +void thread_destroy(void *thread); + +/* + Function: thread_yeild + Yeild the current threads execution slice. +*/ +void thread_yield(); + +/* + Function: thread_detach + Puts the thread in the detached thread, guaranteeing that + resources of the thread will be freed immediately when the + thread terminates. + + Parameters: + thread - Thread to detach +*/ +void thread_detach(void *thread); + +/* Group: Locks */ +typedef void* LOCK; + +LOCK lock_create(); +void lock_destroy(LOCK lock); + +int lock_try(LOCK lock); +void lock_wait(LOCK lock); +void lock_release(LOCK lock); + + +/* Group: Semaphores */ + +#if !defined(CONF_PLATFORM_MACOSX) + #if defined(CONF_FAMILY_UNIX) + #include <semaphore.h> + typedef sem_t SEMAPHORE; + #elif defined(CONF_FAMILY_WINDOWS) + typedef void* SEMAPHORE; + #else + #error missing sempahore implementation + #endif + + void semaphore_init(SEMAPHORE *sem); + void semaphore_wait(SEMAPHORE *sem); + void semaphore_signal(SEMAPHORE *sem); + void semaphore_destroy(SEMAPHORE *sem); +#endif + +/* Group: Timer */ +#ifdef __GNUC__ +/* if compiled with -pedantic-errors it will complain about long + not being a C90 thing. +*/ +__extension__ typedef long long int64; +#else +typedef long long int64; +#endif +/* + Function: time_get + Fetches a sample from a high resolution timer. + + Returns: + Current value of the timer. + + Remarks: + To know how fast the timer is ticking, see <time_freq>. +*/ +int64 time_get(); + +/* + Function: time_freq + Returns the frequency of the high resolution timer. + + Returns: + Returns the frequency of the high resolution timer. +*/ +int64 time_freq(); + +/* + Function: time_timestamp + Retrives the current time as a UNIX timestamp + + Returns: + The time as a UNIX timestamp +*/ +int time_timestamp(); + +/* Group: Network General */ +typedef struct +{ + int type; + int ipv4sock; + int ipv6sock; +} NETSOCKET; + +enum +{ + NETADDR_MAXSTRSIZE = 1+(8*4+7)+1+1+5+1, // [XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX]:XXXXX + + NETTYPE_INVALID = 0, + NETTYPE_IPV4 = 1, + NETTYPE_IPV6 = 2, + NETTYPE_LINK_BROADCAST = 4, + NETTYPE_ALL = NETTYPE_IPV4|NETTYPE_IPV6 +}; + +typedef struct +{ + unsigned int type; + unsigned char ip[16]; + unsigned short port; +} NETADDR; + +/* + Function: net_init + Initiates network functionallity. + + Returns: + Returns 0 on success, + + Remarks: + You must call this function before using any other network + functions. +*/ +int net_init(); + +/* + Function: net_host_lookup + Does a hostname lookup by name and fills out the passed + NETADDR struct with the recieved details. + + Returns: + 0 on success. +*/ +int net_host_lookup(const char *hostname, NETADDR *addr, int types); + +/* + Function: net_addr_comp + Compares two network addresses. + + Parameters: + a - Address to compare + b - Address to compare to. + + Returns: + <0 - Address a is lesser then address b + 0 - Address a is equal to address b + >0 - Address a is greater then address b +*/ +int net_addr_comp(const NETADDR *a, const NETADDR *b); + +/* + Function: net_addr_str + Turns a network address into a representive string. + + Parameters: + addr - Address to turn into a string. + string - Buffer to fill with the string. + max_length - Maximum size of the string. + add_port - add port to string or not + + Remarks: + - The string will always be zero terminated + +*/ +void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port); + +/* + Function: net_addr_from_str + Turns string into a network address. + + Returns: + 0 on success + + Parameters: + addr - Address to fill in. + string - String to parse. +*/ +int net_addr_from_str(NETADDR *addr, const char *string); + +/* Group: Network UDP */ + +/* + Function: net_udp_create + Creates a UDP socket and binds it to a port. + + Parameters: + bindaddr - Address to bind the socket to. + + Returns: + On success it returns an handle to the socket. On failure it + returns NETSOCKET_INVALID. +*/ +NETSOCKET net_udp_create(NETADDR bindaddr); + +/* + Function: net_udp_send + Sends a packet over an UDP socket. + + Parameters: + sock - Socket to use. + addr - Where to send the packet. + data - Pointer to the packet data to send. + size - Size of the packet. + + Returns: + On success it returns the number of bytes sent. Returns -1 + on error. +*/ +int net_udp_send(NETSOCKET sock, const NETADDR *addr, const void *data, int size); + +/* + Function: net_udp_recv + Recives a packet over an UDP socket. + + Parameters: + sock - Socket to use. + addr - Pointer to an NETADDR that will recive the address. + data - Pointer to a buffer that will recive the data. + maxsize - Maximum size to recive. + + Returns: + On success it returns the number of bytes recived. Returns -1 + on error. +*/ +int net_udp_recv(NETSOCKET sock, NETADDR *addr, void *data, int maxsize); + +/* + Function: net_udp_close + Closes an UDP socket. + + Parameters: + sock - Socket to close. + + Returns: + Returns 0 on success. -1 on error. +*/ +int net_udp_close(NETSOCKET sock); + + +/* Group: Network TCP */ + +/* + Function: net_tcp_create + Creates a TCP socket. + + Parameters: + bindaddr - Address to bind the socket to. + + Returns: + On success it returns an handle to the socket. On failure it returns NETSOCKET_INVALID. +*/ +NETSOCKET net_tcp_create(NETADDR bindaddr); + +/* + Function: net_tcp_listen + Makes the socket start listening for new connections. + + Parameters: + sock - Socket to start listen to. + backlog - Size of the queue of incomming connections to keep. + + Returns: + Returns 0 on success. +*/ +int net_tcp_listen(NETSOCKET sock, int backlog); + +/* + Function: net_tcp_accept + Polls a listning socket for a new connection. + + Parameters: + sock - Listning socket to poll. + new_sock - Pointer to a socket to fill in with the new socket. + addr - Pointer to an address that will be filled in the remote address (optional, can be NULL). + + Returns: + Returns a non-negative integer on success. Negative integer on failure. +*/ +int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *addr); + +/* + Function: net_tcp_connect + Connects one socket to another. + + Parameters: + sock - Socket to connect. + addr - Address to connect to. + + Returns: + Returns 0 on success. + +*/ +int net_tcp_connect(NETSOCKET sock, const NETADDR *addr); + +/* + Function: net_tcp_send + Sends data to a TCP stream. + + Parameters: + sock - Socket to send data to. + data - Pointer to the data to send. + size - Size of the data to send. + + Returns: + Number of bytes sent. Negative value on failure. +*/ +int net_tcp_send(NETSOCKET sock, const void *data, int size); + +/* + Function: net_tcp_recv + Recvives data from a TCP stream. + + Parameters: + sock - Socket to recvive data from. + data - Pointer to a buffer to write the data to + max_size - Maximum of data to write to the buffer. + + Returns: + Number of bytes recvived. Negative value on failure. When in + non-blocking mode, it returns 0 when there is no more data to + be fetched. +*/ +int net_tcp_recv(NETSOCKET sock, void *data, int maxsize); + +/* + Function: net_tcp_close + Closes a TCP socket. + + Parameters: + sock - Socket to close. + + Returns: + Returns 0 on success. Negative value on failure. +*/ +int net_tcp_close(NETSOCKET sock); + +/* Group: Strings */ + +/* + Function: str_append + Appends a string to another. + + Parameters: + dst - Pointer to a buffer that contains a string. + src - String to append. + dst_size - Size of the buffer of the dst string. + + Remarks: + - The strings are treated as zero-termineted strings. + - Garantees that dst string will contain zero-termination. +*/ +void str_append(char *dst, const char *src, int dst_size); + +/* + Function: str_copy + Copies a string to another. + + Parameters: + dst - Pointer to a buffer that shall recive the string. + src - String to be copied. + dst_size - Size of the buffer dst. + + Remarks: + - The strings are treated as zero-termineted strings. + - Garantees that dst string will contain zero-termination. +*/ +void str_copy(char *dst, const char *src, int dst_size); + +/* + Function: str_length + Returns the length of a zero terminated string. + + Parameters: + str - Pointer to the string. + + Returns: + Length of string in bytes excluding the zero termination. +*/ +int str_length(const char *str); + +/* + Function: str_format + Performs printf formating into a buffer. + + Parameters: + buffer - Pointer to the buffer to recive the formated string. + buffer_size - Size of the buffer. + format - printf formating string. + ... - Parameters for the formating. + + Remarks: + - See the C manual for syntax for the printf formating string. + - The strings are treated as zero-termineted strings. + - Garantees that dst string will contain zero-termination. +*/ +void str_format(char *buffer, int buffer_size, const char *format, ...); + +/* + Function: str_sanitize_strong + Replaces all characters below 32 and above 127 with whitespace. + + Parameters: + str - String to sanitize. + + Remarks: + - The strings are treated as zero-termineted strings. +*/ +void str_sanitize_strong(char *str); + +/* + Function: str_sanitize_cc + Replaces all characters below 32 with whitespace. + + Parameters: + str - String to sanitize. + + Remarks: + - The strings are treated as zero-termineted strings. +*/ +void str_sanitize_cc(char *str); + +/* + Function: str_sanitize + Replaces all characters below 32 with whitespace with + exception to \t, \n and \r. + + Parameters: + str - String to sanitize. + + Remarks: + - The strings are treated as zero-termineted strings. +*/ +void str_sanitize(char *str); + +/* + Function: str_skip_to_whitespace + Skips leading non-whitespace characters(all but ' ', '\t', '\n', '\r'). + + Parameters: + str - Pointer to the string. + + Returns: + Pointer to the first whitespace character found + within the string. + + Remarks: + - The strings are treated as zero-termineted strings. +*/ +char *str_skip_to_whitespace(char *str); + +/* + Function: str_skip_whitespaces + Skips leading whitespace characters(' ', '\t', '\n', '\r'). + + Parameters: + str - Pointer to the string. + + Returns: + Pointer to the first non-whitespace character found + within the string. + + Remarks: + - The strings are treated as zero-termineted strings. +*/ +char *str_skip_whitespaces(char *str); + +/* + Function: str_comp_nocase + Compares to strings case insensitive. + + Parameters: + a - String to compare. + b - String to compare. + + Returns: + <0 - String a is lesser then string b + 0 - String a is equal to string b + >0 - String a is greater then string b + + Remarks: + - Only garanted to work with a-z/A-Z. + - The strings are treated as zero-termineted strings. +*/ +int str_comp_nocase(const char *a, const char *b); + +/* + Function: str_comp_nocase_num + Compares up to num characters of two strings case insensitive. + + Parameters: + a - String to compare. + b - String to compare. + num - Maximum characters to compare + + Returns: + <0 - String a is lesser than string b + 0 - String a is equal to string b + >0 - String a is greater than string b + + Remarks: + - Only garanted to work with a-z/A-Z. + - The strings are treated as zero-termineted strings. +*/ +int str_comp_nocase_num(const char *a, const char *b, const int num); + +/* + Function: str_comp + Compares to strings case sensitive. + + Parameters: + a - String to compare. + b - String to compare. + + Returns: + <0 - String a is lesser then string b + 0 - String a is equal to string b + >0 - String a is greater then string b + + Remarks: + - The strings are treated as zero-termineted strings. +*/ +int str_comp(const char *a, const char *b); + +/* + Function: str_comp_num + Compares up to num characters of two strings case sensitive. + + Parameters: + a - String to compare. + b - String to compare. + num - Maximum characters to compare + + Returns: + <0 - String a is lesser then string b + 0 - String a is equal to string b + >0 - String a is greater then string b + + Remarks: + - The strings are treated as zero-termineted strings. +*/ +int str_comp_num(const char *a, const char *b, const int num); + +/* + Function: str_comp_filenames + Compares two strings case sensitive, digit chars will be compared as numbers. + + Parameters: + a - String to compare. + b - String to compare. + + Returns: + <0 - String a is lesser then string b + 0 - String a is equal to string b + >0 - String a is greater then string b + + Remarks: + - The strings are treated as zero-termineted strings. +*/ +int str_comp_filenames(const char *a, const char *b); + +/* + Function: str_find_nocase + Finds a string inside another string case insensitive. + + Parameters: + haystack - String to search in + needle - String to search for + + Returns: + A pointer into haystack where the needle was found. + Returns NULL of needle could not be found. + + Remarks: + - Only garanted to work with a-z/A-Z. + - The strings are treated as zero-termineted strings. +*/ +const char *str_find_nocase(const char *haystack, const char *needle); + +/* + Function: str_find + Finds a string inside another string case sensitive. + + Parameters: + haystack - String to search in + needle - String to search for + + Returns: + A pointer into haystack where the needle was found. + Returns NULL of needle could not be found. + + Remarks: + - The strings are treated as zero-termineted strings. +*/ +const char *str_find(const char *haystack, const char *needle); + +/* + Function: str_hex + Takes a datablock and generates a hexstring of it. + + Parameters: + dst - Buffer to fill with hex data + dst_size - size of the buffer + data - Data to turn into hex + data - Size of the data + + Remarks: + - The desination buffer will be zero-terminated +*/ +void str_hex(char *dst, int dst_size, const void *data, int data_size); + +/* + Function: str_timestamp + Copies a time stamp in the format year-month-day_hour-minute-second to the string. + + Parameters: + buffer - Pointer to a buffer that shall receive the time stamp string. + buffer_size - Size of the buffer. + + Remarks: + - Guarantees that buffer string will contain zero-termination. +*/ +void str_timestamp(char *buffer, int buffer_size); + +/* Group: Filesystem */ + +/* + Function: fs_listdir + Lists the files in a directory + + Parameters: + dir - Directory to list + cb - Callback function to call for each entry + type - Type of the directory + user - Pointer to give to the callback + + Returns: + Always returns 0. +*/ +typedef int (*FS_LISTDIR_CALLBACK)(const char *name, int is_dir, int dir_type, void *user); +int fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user); + +/* + Function: fs_makedir + Creates a directory + + Parameters: + path - Directory to create + + Returns: + Returns 0 on success. Negative value on failure. + + Remarks: + Does not create several directories if needed. "a/b/c" will result + in a failure if b or a does not exist. +*/ +int fs_makedir(const char *path); + +/* + Function: fs_storage_path + Fetches per user configuration directory. + + Returns: + Returns 0 on success. Negative value on failure. + + Remarks: + - Returns ~/.appname on UNIX based systems + - Returns ~/Library/Applications Support/appname on Mac OS X + - Returns %APPDATA%/Appname on Windows based systems +*/ +int fs_storage_path(const char *appname, char *path, int max); + +/* + Function: fs_is_dir + Checks if directory exists + + Returns: + Returns 1 on success, 0 on failure. +*/ +int fs_is_dir(const char *path); + +/* + Function: fs_chdir + Changes current working directory + + Returns: + Returns 0 on success, 1 on failure. +*/ +int fs_chdir(const char *path); + +/* + Function: fs_getcwd + Gets the current working directory. + + Returns: + Returns a pointer to the buffer on success, 0 on failure. +*/ +char *fs_getcwd(char *buffer, int buffer_size); + +/* + Function: fs_parent_dir + Get the parent directory of a directory + + Parameters: + path - The directory string + + Returns: + Returns 0 on success, 1 on failure. + + Remarks: + - The string is treated as zero-termineted string. +*/ +int fs_parent_dir(char *path); + +/* + Function: fs_remove + Deletes the file with the specified name. + + Parameters: + filename - The file to delete + + Returns: + Returns 0 on success, 1 on failure. + + Remarks: + - The strings are treated as zero-terminated strings. +*/ +int fs_remove(const char *filename); + +/* + Function: fs_rename + Renames the file or directory. If the paths differ the file will be moved. + + Parameters: + oldname - The actual name + newname - The new name + + Returns: + Returns 0 on success, 1 on failure. + + Remarks: + - The strings are treated as zero-terminated strings. +*/ +int fs_rename(const char *oldname, const char *newname); + +/* + Group: Undocumented +*/ + + +/* + Function: net_tcp_connect_non_blocking + + DOCTODO: serp +*/ +int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr); + +/* + Function: net_set_non_blocking + + DOCTODO: serp +*/ +int net_set_non_blocking(NETSOCKET sock); + +/* + Function: net_set_non_blocking + + DOCTODO: serp +*/ +int net_set_blocking(NETSOCKET sock); + +/* + Function: net_errno + + DOCTODO: serp +*/ +int net_errno(); + +/* + Function: net_would_block + + DOCTODO: serp +*/ +int net_would_block(); + +int net_socket_read_wait(NETSOCKET sock, int time); + +void mem_debug_dump(IOHANDLE file); + +void swap_endian(void *data, unsigned elem_size, unsigned num); + + +typedef void (*DBG_LOGGER)(const char *line); +void dbg_logger(DBG_LOGGER logger); + +void dbg_logger_stdout(); +void dbg_logger_debugger(); +void dbg_logger_file(const char *filename); + +typedef struct +{ + int allocated; + int active_allocations; + int total_allocations; +} MEMSTATS; + +const MEMSTATS *mem_stats(); + +typedef struct +{ + int sent_packets; + int sent_bytes; + int recv_packets; + int recv_bytes; +} NETSTATS; + + +void net_stats(NETSTATS *stats); + +int str_toint(const char *str); +float str_tofloat(const char *str); +int str_isspace(char c); +char str_uppercase(char c); +unsigned str_quickhash(const char *str); + +/* + Function: gui_messagebox + Display plain OS-dependent message box + + Parameters: + title - title of the message box + message - text to display +*/ +void gui_messagebox(const char *title, const char *message); + + +/* + Function: str_utf8_rewind + Moves a cursor backwards in an utf8 string + + Parameters: + str - utf8 string + cursor - position in the string + + Returns: + New cursor position. + + Remarks: + - Won't move the cursor less then 0 +*/ +int str_utf8_rewind(const char *str, int cursor); + +/* + Function: str_utf8_forward + Moves a cursor forwards in an utf8 string + + Parameters: + str - utf8 string + cursor - position in the string + + Returns: + New cursor position. + + Remarks: + - Won't move the cursor beyond the zero termination marker +*/ +int str_utf8_forward(const char *str, int cursor); + +/* + Function: str_utf8_decode + Decodes an utf8 character + + Parameters: + ptr - pointer to an utf8 string. this pointer will be moved forward + + Returns: + Unicode value for the character. -1 for invalid characters and 0 for end of string. + + Remarks: + - This function will also move the pointer forward. +*/ +int str_utf8_decode(const char **ptr); + +/* + Function: str_utf8_encode + Encode an utf8 character + + Parameters: + ptr - Pointer to a buffer that should recive the data. Should be able to hold at least 4 bytes. + + Returns: + Number of bytes put into the buffer. + + Remarks: + - Does not do zero termination of the string. +*/ +int str_utf8_encode(char *ptr, int chr); + +/* + Function: str_utf8_check + Checks if a strings contains just valid utf8 characters. + + Parameters: + str - Pointer to a possible utf8 string. + + Returns: + 0 - invalid characters found. + 1 - only valid characters found. + + Remarks: + - The string is treated as zero-terminated utf8 string. +*/ +int str_utf8_check(const char *str); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/server/obj/.gitignore b/server/obj/.gitignore new file mode 100644 index 0000000..1530978 --- /dev/null +++ b/server/obj/.gitignore @@ -0,0 +1 @@ +*.o \ No newline at end of file diff --git a/server/src/argparse.c b/server/src/argparse.c new file mode 100644 index 0000000..b581a2f --- /dev/null +++ b/server/src/argparse.c @@ -0,0 +1,322 @@ +#include "argparse.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#define OPT_UNSET 1 + +static const char * +prefix_skip(const char *str, const char *prefix) +{ + size_t len = strlen(prefix); + return strncmp(str, prefix, len) ? NULL : str + len; +} + +int +prefix_cmp(const char *str, const char *prefix) +{ + for (;; str++, prefix++) + if (!*prefix) + return 0; + else if (*str != *prefix) + return (unsigned char)*prefix - (unsigned char)*str; +} + +static void +argparse_error(struct argparse *this_, const struct argparse_option *opt, + const char *reason) +{ + if (!strncmp(this_->argv[0], "--", 2)) { + fprintf(stderr, "error: option `%s` %s\n", opt->long_name, reason); + exit(-1); + } else { + fprintf(stderr, "error: option `%c` %s\n", opt->short_name, reason); + exit(-1); + } +} + +static int +argparse_getvalue(struct argparse *this_, const struct argparse_option *opt, + int flags) +{ + const char *s = NULL; + if (!opt->value) + goto skipped; + switch (opt->type) { + case ARGPARSE_OPT_BOOLEAN: + if (flags & OPT_UNSET) { + *(int *)opt->value = *(int *)opt->value - 1; + } else { + *(int *)opt->value = *(int *)opt->value + 1; + } + if (*(int *)opt->value < 0) { + *(int *)opt->value = 0; + } + break; + case ARGPARSE_OPT_BIT: + if (flags & OPT_UNSET) { + *(int *)opt->value &= ~opt->data; + } else { + *(int *)opt->value |= opt->data; + } + break; + case ARGPARSE_OPT_STRING: + if (this_->optvalue) { + *(const char **)opt->value = this_->optvalue; + this_->optvalue = NULL; + } else if (this_->argc > 1) { + this_->argc--; + *(const char **)opt->value = *++this_->argv; + } else { + argparse_error(this_, opt, "requires a value"); + } + break; + case ARGPARSE_OPT_INTEGER: + if (this_->optvalue) { + *(int *)opt->value = strtol(this_->optvalue, (char **)&s, 0); + this_->optvalue = NULL; + } else if (this_->argc > 1) { + this_->argc--; + *(int *)opt->value = strtol(*++this_->argv, (char **)&s, 0); + } else { + argparse_error(this_, opt, "requires a value"); + } + if (*s) + argparse_error(this_, opt, "expects a numerical value"); + break; + default: + assert(0); + } + +skipped: + if (opt->callback) { + return opt->callback(this_, opt); + } + + return 0; +} + +static void +argparse_options_check(const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + switch (options->type) { + case ARGPARSE_OPT_END: + case ARGPARSE_OPT_BOOLEAN: + case ARGPARSE_OPT_BIT: + case ARGPARSE_OPT_INTEGER: + case ARGPARSE_OPT_STRING: + continue; + default: + fprintf(stderr, "wrong option type: %d", options->type); + break; + } + } +} + +static int +argparse_short_opt(struct argparse *this_, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + if (options->short_name == *this_->optvalue) { + this_->optvalue = this_->optvalue[1] ? this_->optvalue + 1 : NULL; + return argparse_getvalue(this_, options, 0); + } + } + return -2; +} + +static int +argparse_long_opt(struct argparse *this_, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + const char *rest; + int opt_flags = 0; + if (!options->long_name) + continue; + + rest = prefix_skip(this_->argv[0] + 2, options->long_name); + if (!rest) { + // Negation allowed? + if (options->flags & OPT_NONEG) { + continue; + } + // Only boolean/bit allow negation. + if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != ARGPARSE_OPT_BIT) { + continue; + } + + if (!prefix_cmp(this_->argv[0] + 2, "no-")) { + rest = prefix_skip(this_->argv[0] + 2 + 3, options->long_name); + if (!rest) + continue; + opt_flags |= OPT_UNSET; + } else { + continue; + } + } + if (*rest) { + if (*rest != '=') + continue; + this_->optvalue = rest + 1; + } + return argparse_getvalue(this_, options, opt_flags); + } + return -2; +} + +int +argparse_init(struct argparse *this_, struct argparse_option *options, + const char *usage, int flags) +{ + memset(this_, 0, sizeof(*this_)); + this_->options = options; + this_->usage = usage; + this_->flags = flags; + return 0; +} + +int +argparse_parse(struct argparse *this_, int argc, const char **argv) +{ + this_->argc = argc - 1; + this_->argv = argv + 1; + this_->out = argv; + + argparse_options_check(this_->options); + + for (; this_->argc; this_->argc--, this_->argv++) { + const char *arg = this_->argv[0]; + if (arg[0] != '-' || !arg[1]) { + if (this_->flags & ARGPARSE_STOP_AT_NON_OPTION) { + goto end; + } + // if it's not option or is a single char '-', copy verbatimly + this_->out[this_->cpidx++] = this_->argv[0]; + continue; + } + // short option + if (arg[1] != '-') { + this_->optvalue = arg + 1; + switch (argparse_short_opt(this_, this_->options)) { + case -1: + break; + case -2: + goto unknown; + } + while (this_->optvalue) { + switch (argparse_short_opt(this_, this_->options)) { + case -1: + break; + case -2: + goto unknown; + } + } + continue; + } + // if '--' presents + if (!arg[2]) { + this_->argc--; + this_->argv++; + break; + } + // long option + switch (argparse_long_opt(this_, this_->options)) { + case -1: + break; + case -2: + goto unknown; + } + continue; + +unknown: + fprintf(stderr, "error: unknown option `%s`\n", this_->argv[0]); + argparse_usage(this_); + exit(0); + } + +end: + memmove(this_->out + this_->cpidx, this_->argv, + this_->argc * sizeof(*this_->out)); + this_->out[this_->cpidx + this_->argc] = NULL; + + return this_->cpidx + this_->argc; +} + +void +argparse_usage(struct argparse *this_) +{ + fprintf(stdout, "Usage: %s\n", this_->usage); + fputc('\n', stdout); + + const struct argparse_option *options; + + // figure out best width + size_t usage_opts_width = 0; + size_t len; + options = this_->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + len = 0; + if ((options)->short_name) { + len += 2; + } + if ((options)->short_name && (options)->long_name) { + len += 2; // separator ", " + } + if ((options)->long_name) { + len += strlen((options)->long_name) + 2; + } + if (options->type == ARGPARSE_OPT_INTEGER) { + len += strlen("=<int>"); + } else if (options->type == ARGPARSE_OPT_STRING) { + len += strlen("=<str>"); + } + len = ceil((float)len / 4) * 4; + if (usage_opts_width < len) { + usage_opts_width = len; + } + } + usage_opts_width += 4; // 4 spaces prefix + + options = this_->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + size_t pos; + int pad; + pos = fprintf(stdout, " "); + if (options->short_name) { + pos += fprintf(stdout, "-%c", options->short_name); + } + if (options->long_name && options->short_name) { + pos += fprintf(stdout, ", "); + } + if (options->long_name) { + pos += fprintf(stdout, "--%s", options->long_name); + } + if (options->type == ARGPARSE_OPT_INTEGER) { + pos += fprintf(stdout, "=<int>"); + } else if (options->type == ARGPARSE_OPT_STRING) { + pos += fprintf(stdout, "=<str>"); + } + if (pos <= usage_opts_width) { + pad = usage_opts_width - pos; + } else { + fputc('\n', stdout); + pad = usage_opts_width; + } + fprintf(stdout, "%*s%s\n", pad + 2, "", options->help); + } +} + +int +argparse_help_cb(struct argparse *this_, const struct argparse_option *option) +{ + (void)option; + argparse_usage(this_); + exit(0); + return 0; +} + +#if defined(__cplusplus) +} +#endif diff --git a/server/src/json.c b/server/src/json.c new file mode 100644 index 0000000..ded29e0 --- /dev/null +++ b/server/src/json.c @@ -0,0 +1,949 @@ +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. + * https://github.com/udp/json-parser + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "json.h" + +#ifdef _MSC_VER + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif +#endif + +#ifdef __cplusplus + const struct _json_value json_value_none; /* zero-d by ctor */ +#else + const struct _json_value json_value_none = { 0 }; +#endif + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <math.h> + +typedef unsigned short json_uchar; + +static unsigned char hex_value (json_char c) +{ + if (isdigit(c)) + return c - '0'; + + switch (c) { + case 'a': case 'A': return 0x0A; + case 'b': case 'B': return 0x0B; + case 'c': case 'C': return 0x0C; + case 'd': case 'D': return 0x0D; + case 'e': case 'E': return 0x0E; + case 'f': case 'F': return 0x0F; + default: return 0xFF; + } +} + +typedef struct +{ + unsigned long used_memory; + + unsigned int uint_max; + unsigned long ulong_max; + + json_settings settings; + int first_pass; + +} json_state; + +static void * default_alloc (size_t size, int zero, void * user_data) +{ + return zero ? calloc (1, size) : malloc (size); +} + +static void default_free (void * ptr, void * user_data) +{ + free (ptr); +} + +static void * json_alloc (json_state * state, unsigned long size, int zero) +{ + if ((state->ulong_max - state->used_memory) < size) + return 0; + + if (state->settings.max_memory + && (state->used_memory += size) > state->settings.max_memory) + { + return 0; + } + + return state->settings.mem_alloc (size, zero, state->settings.user_data); +} + +static int new_value + (json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type) +{ + json_value * value; + int values_size; + + if (!state->first_pass) + { + value = *top = *alloc; + *alloc = (*alloc)->_reserved.next_alloc; + + if (!*root) + *root = value; + + switch (value->type) + { + case json_array: + + if (! (value->u.array.values = (json_value **) json_alloc + (state, value->u.array.length * sizeof (json_value *), 0)) ) + { + return 0; + } + + value->u.array.length = 0; + break; + + case json_object: + + values_size = sizeof (*value->u.object.values) * value->u.object.length; + + if (! ((*(void **) &value->u.object.values) = json_alloc + (state, values_size + ((unsigned long) value->u.object.values), 0)) ) + { + return 0; + } + + value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; + + value->u.object.length = 0; + break; + + case json_string: + + if (! (value->u.string.ptr = (json_char *) json_alloc + (state, (value->u.string.length + 1) * sizeof (json_char), 0)) ) + { + return 0; + } + + value->u.string.length = 0; + break; + + default: + break; + }; + + return 1; + } + + value = (json_value *) json_alloc (state, sizeof (json_value), 1); + + if (!value) + return 0; + + if (!*root) + *root = value; + + value->type = type; + value->parent = *top; + + if (*alloc) + (*alloc)->_reserved.next_alloc = value; + + *alloc = *top = value; + + return 1; +} + +#define e_off \ + ((int) (i - cur_line_begin)) + +#define whitespace \ + case '\n': ++ cur_line; cur_line_begin = i; \ + case ' ': case '\t': case '\r' + +#define string_add(b) \ + do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0); + +static const long + flag_next = 1 << 0, + flag_reproc = 1 << 1, + flag_need_comma = 1 << 2, + flag_seek_value = 1 << 3, + flag_escaped = 1 << 4, + flag_string = 1 << 5, + flag_need_colon = 1 << 6, + flag_done = 1 << 7, + flag_num_negative = 1 << 8, + flag_num_zero = 1 << 9, + flag_num_e = 1 << 10, + flag_num_e_got_sign = 1 << 11, + flag_num_e_negative = 1 << 12, + flag_line_comment = 1 << 13, + flag_block_comment = 1 << 14; + +json_value * json_parse_ex (json_settings * settings, + const json_char * json, + size_t length, + char * error_buf) +{ + json_char error [json_error_max]; + unsigned int cur_line; + const json_char * cur_line_begin, * i, * end; + json_value * top, * root, * alloc = 0; + json_state state = { 0 }; + long flags; + long num_digits = 0, num_e = 0; + json_int_t num_fraction = 0; + + /* Skip UTF-8 BOM + */ + if (length >= 3 && ((unsigned char) json [0]) == 0xEF + && ((unsigned char) json [1]) == 0xBB + && ((unsigned char) json [2]) == 0xBF) + { + json += 3; + length -= 3; + } + + error[0] = '\0'; + end = (json + length); + + memcpy (&state.settings, settings, sizeof (json_settings)); + + if (!state.settings.mem_alloc) + state.settings.mem_alloc = default_alloc; + + if (!state.settings.mem_free) + state.settings.mem_free = default_free; + + memset (&state.uint_max, 0xFF, sizeof (state.uint_max)); + memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max)); + + state.uint_max -= 8; /* limit of how much can be added before next check */ + state.ulong_max -= 8; + + for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass) + { + json_uchar uchar; + unsigned char uc_b1, uc_b2, uc_b3, uc_b4; + json_char * string = 0; + unsigned int string_length = 0; + + top = root = 0; + flags = flag_seek_value; + + cur_line = 1; + cur_line_begin = json; + + for (i = json ;; ++ i) + { + json_char b = (i == end ? 0 : *i); + + if (flags & flag_string) + { + if (!b) + { sprintf (error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off); + goto e_failed; + } + + if (string_length > state.uint_max) + goto e_overflow; + + if (flags & flag_escaped) + { + flags &= ~ flag_escaped; + + switch (b) + { + case 'b': string_add ('\b'); break; + case 'f': string_add ('\f'); break; + case 'n': string_add ('\n'); break; + case 'r': string_add ('\r'); break; + case 't': string_add ('\t'); break; + case 'u': + + if (end - i < 4 || + (uc_b1 = hex_value (*++ i)) == 0xFF || (uc_b2 = hex_value (*++ i)) == 0xFF + || (uc_b3 = hex_value (*++ i)) == 0xFF || (uc_b4 = hex_value (*++ i)) == 0xFF) + { + sprintf (error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off); + goto e_failed; + } + + uc_b1 = uc_b1 * 16 + uc_b2; + uc_b2 = uc_b3 * 16 + uc_b4; + + uchar = ((json_char) uc_b1) * 256 + uc_b2; + + if (sizeof (json_char) >= sizeof (json_uchar) || (uc_b1 == 0 && uc_b2 <= 0x7F)) + { + string_add ((json_char) uchar); + break; + } + + if (uchar <= 0x7FF) + { + if (state.first_pass) + string_length += 2; + else + { string [string_length ++] = 0xC0 | ((uc_b2 & 0xC0) >> 6) | ((uc_b1 & 0x7) << 2); + string [string_length ++] = 0x80 | (uc_b2 & 0x3F); + } + + break; + } + + if (state.first_pass) + string_length += 3; + else + { string [string_length ++] = 0xE0 | ((uc_b1 & 0xF0) >> 4); + string [string_length ++] = 0x80 | ((uc_b1 & 0xF) << 2) | ((uc_b2 & 0xC0) >> 6); + string [string_length ++] = 0x80 | (uc_b2 & 0x3F); + } + + break; + + default: + string_add (b); + }; + + continue; + } + + if (b == '\\') + { + flags |= flag_escaped; + continue; + } + + if (b == '"') + { + if (!state.first_pass) + string [string_length] = 0; + + flags &= ~ flag_string; + string = 0; + + switch (top->type) + { + case json_string: + + top->u.string.length = string_length; + flags |= flag_next; + + break; + + case json_object: + + if (state.first_pass) + (*(json_char **) &top->u.object.values) += string_length + 1; + else + { + top->u.object.values [top->u.object.length].name + = (json_char *) top->_reserved.object_mem; + + top->u.object.values [top->u.object.length].name_length + = string_length; + + (*(json_char **) &top->_reserved.object_mem) += string_length + 1; + } + + flags |= flag_seek_value | flag_need_colon; + continue; + + default: + break; + }; + } + else + { + string_add (b); + continue; + } + } + + if (state.settings.settings & json_enable_comments) + { + if (flags & (flag_line_comment | flag_block_comment)) + { + if (flags & flag_line_comment) + { + if (b == '\r' || b == '\n' || !b) + { + flags &= ~ flag_line_comment; + -- i; /* so null can be reproc'd */ + } + + continue; + } + + if (flags & flag_block_comment) + { + if (!b) + { sprintf (error, "%d:%d: Unexpected EOF in block comment", cur_line, e_off); + goto e_failed; + } + + if (b == '*' && i < (end - 1) && i [1] == '/') + { + flags &= ~ flag_block_comment; + ++ i; /* skip closing sequence */ + } + + continue; + } + } + else if (b == '/') + { + if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object) + { + sprintf (error, "%d:%d: Comment not allowed here", cur_line, e_off); + goto e_failed; + } + + if (++ i == end) + { sprintf (error, "%d:%d: EOF unexpected", cur_line, e_off); + goto e_failed; + } + + switch (b = *i) + { + case '/': + flags |= flag_line_comment; + continue; + + case '*': + flags |= flag_block_comment; + continue; + + default: + sprintf (error, "%d:%d: Unexpected `%c` in comment opening sequence", cur_line, e_off, b); + goto e_failed; + }; + } + } + + if (flags & flag_done) + { + if (!b) + break; + + switch (b) + { + whitespace: + continue; + + default: + sprintf (error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b); + goto e_failed; + }; + } + + if (flags & flag_seek_value) + { + switch (b) + { + whitespace: + continue; + + case ']': + + if (top->type == json_array) + flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next; + else + { sprintf (error, "%d:%d: Unexpected ]", cur_line, e_off); + goto e_failed; + } + + break; + + default: + + if (flags & flag_need_comma) + { + if (b == ',') + { flags &= ~ flag_need_comma; + continue; + } + else + { sprintf (error, "%d:%d: Expected , before %c", cur_line, e_off, b); + goto e_failed; + } + } + + if (flags & flag_need_colon) + { + if (b == ':') + { flags &= ~ flag_need_colon; + continue; + } + else + { sprintf (error, "%d:%d: Expected : before %c", cur_line, e_off, b); + goto e_failed; + } + } + + flags &= ~ flag_seek_value; + + switch (b) + { + case '{': + + if (!new_value (&state, &top, &root, &alloc, json_object)) + goto e_alloc_failure; + + continue; + + case '[': + + if (!new_value (&state, &top, &root, &alloc, json_array)) + goto e_alloc_failure; + + flags |= flag_seek_value; + continue; + + case '"': + + if (!new_value (&state, &top, &root, &alloc, json_string)) + goto e_alloc_failure; + + flags |= flag_string; + + string = top->u.string.ptr; + string_length = 0; + + continue; + + case 't': + + if ((end - i) < 3 || *(++ i) != 'r' || *(++ i) != 'u' || *(++ i) != 'e') + goto e_unknown_value; + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + top->u.boolean = 1; + + flags |= flag_next; + break; + + case 'f': + + if ((end - i) < 4 || *(++ i) != 'a' || *(++ i) != 'l' || *(++ i) != 's' || *(++ i) != 'e') + goto e_unknown_value; + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + case 'n': + + if ((end - i) < 3 || *(++ i) != 'u' || *(++ i) != 'l' || *(++ i) != 'l') + goto e_unknown_value; + + if (!new_value (&state, &top, &root, &alloc, json_null)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + default: + + if (isdigit (b) || b == '-') + { + if (!new_value (&state, &top, &root, &alloc, json_integer)) + goto e_alloc_failure; + + if (!state.first_pass) + { + while (isdigit (b) || b == '+' || b == '-' + || b == 'e' || b == 'E' || b == '.') + { + if ( (++ i) == end) + { + b = 0; + break; + } + + b = *i; + } + + flags |= flag_next | flag_reproc; + break; + } + + flags &= ~ (flag_num_negative | flag_num_e | + flag_num_e_got_sign | flag_num_e_negative | + flag_num_zero); + + num_digits = 0; + num_fraction = 0; + num_e = 0; + + if (b != '-') + { + flags |= flag_reproc; + break; + } + + flags |= flag_num_negative; + continue; + } + else + { sprintf (error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b); + goto e_failed; + } + }; + }; + } + else + { + switch (top->type) + { + case json_object: + + switch (b) + { + whitespace: + continue; + + case '"': + + if (flags & flag_need_comma) + { + sprintf (error, "%d:%d: Expected , before \"", cur_line, e_off); + goto e_failed; + } + + flags |= flag_string; + + string = (json_char *) top->_reserved.object_mem; + string_length = 0; + + break; + + case '}': + + flags = (flags & ~ flag_need_comma) | flag_next; + break; + + case ',': + + if (flags & flag_need_comma) + { + flags &= ~ flag_need_comma; + break; + } + + default: + + sprintf (error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b); + goto e_failed; + }; + + break; + + case json_integer: + case json_double: + + if (isdigit (b)) + { + ++ num_digits; + + if (top->type == json_integer || flags & flag_num_e) + { + if (! (flags & flag_num_e)) + { + if (flags & flag_num_zero) + { sprintf (error, "%d:%d: Unexpected `0` before `%c`", cur_line, e_off, b); + goto e_failed; + } + + if (num_digits == 1 && b == '0') + flags |= flag_num_zero; + } + else + { + flags |= flag_num_e_got_sign; + num_e = (num_e * 10) + (b - '0'); + continue; + } + + top->u.integer = (top->u.integer * 10) + (b - '0'); + continue; + } + + num_fraction = (num_fraction * 10) + (b - '0'); + continue; + } + + if (b == '+' || b == '-') + { + if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign)) + { + flags |= flag_num_e_got_sign; + + if (b == '-') + flags |= flag_num_e_negative; + + continue; + } + } + else if (b == '.' && top->type == json_integer) + { + if (!num_digits) + { sprintf (error, "%d:%d: Expected digit before `.`", cur_line, e_off); + goto e_failed; + } + + top->type = json_double; + top->u.dbl = (double) top->u.integer; + + num_digits = 0; + continue; + } + + if (! (flags & flag_num_e)) + { + if (top->type == json_double) + { + if (!num_digits) + { sprintf (error, "%d:%d: Expected digit after `.`", cur_line, e_off); + goto e_failed; + } + + top->u.dbl += ((double) num_fraction) / (pow (10, (double) num_digits)); + } + + if (b == 'e' || b == 'E') + { + flags |= flag_num_e; + + if (top->type == json_integer) + { + top->type = json_double; + top->u.dbl = (double) top->u.integer; + } + + num_digits = 0; + flags &= ~ flag_num_zero; + + continue; + } + } + else + { + if (!num_digits) + { sprintf (error, "%d:%d: Expected digit after `e`", cur_line, e_off); + goto e_failed; + } + + top->u.dbl *= pow (10, (double) (flags & flag_num_e_negative ? - num_e : num_e)); + } + + if (flags & flag_num_negative) + { + if (top->type == json_integer) + top->u.integer = - top->u.integer; + else + top->u.dbl = - top->u.dbl; + } + + flags |= flag_next | flag_reproc; + break; + + default: + break; + }; + } + + if (flags & flag_reproc) + { + flags &= ~ flag_reproc; + -- i; + } + + if (flags & flag_next) + { + flags = (flags & ~ flag_next) | flag_need_comma; + + if (!top->parent) + { + /* root value done */ + + flags |= flag_done; + continue; + } + + if (top->parent->type == json_array) + flags |= flag_seek_value; + + if (!state.first_pass) + { + json_value * parent = top->parent; + + switch (parent->type) + { + case json_object: + + parent->u.object.values + [parent->u.object.length].value = top; + + break; + + case json_array: + + parent->u.array.values + [parent->u.array.length] = top; + + break; + + default: + break; + }; + } + + if ( (++ top->parent->u.array.length) > state.uint_max) + goto e_overflow; + + top = top->parent; + + continue; + } + } + + alloc = root; + } + + return root; + +e_unknown_value: + + sprintf (error, "%d:%d: Unknown value", cur_line, e_off); + goto e_failed; + +e_alloc_failure: + + strcpy (error, "Memory allocation failure"); + goto e_failed; + +e_overflow: + + sprintf (error, "%d:%d: Too long (caught overflow)", cur_line, e_off); + goto e_failed; + +e_failed: + + if (error_buf) + { + if (*error) + strcpy (error_buf, error); + else + strcpy (error_buf, "Unknown error"); + } + + if (state.first_pass) + alloc = root; + + while (alloc) + { + top = alloc->_reserved.next_alloc; + state.settings.mem_free (alloc, state.settings.user_data); + alloc = top; + } + + if (!state.first_pass) + json_value_free_ex (&state.settings, root); + + return 0; +} + +json_value * json_parse (const json_char * json, size_t length) +{ + json_settings settings = { 0 }; + return json_parse_ex (&settings, json, length, 0); +} + +void json_value_free_ex (json_settings * settings, json_value * value) +{ + json_value * cur_value; + + if (!value) + return; + + value->parent = 0; + + while (value) + { + switch (value->type) + { + case json_array: + + if (!value->u.array.length) + { + settings->mem_free (value->u.array.values, settings->user_data); + break; + } + + value = value->u.array.values [-- value->u.array.length]; + continue; + + case json_object: + + if (!value->u.object.length) + { + settings->mem_free (value->u.object.values, settings->user_data); + break; + } + + value = value->u.object.values [-- value->u.object.length].value; + continue; + + case json_string: + + settings->mem_free (value->u.string.ptr, settings->user_data); + break; + + default: + break; + }; + + cur_value = value; + value = value->parent; + settings->mem_free (cur_value, settings->user_data); + } +} + +void json_value_free (json_value * value) +{ + json_settings settings = { 0 }; + settings.mem_free = default_free; + json_value_free_ex (&settings, value); +} diff --git a/server/src/main.cpp b/server/src/main.cpp new file mode 100644 index 0000000..5ba7025 --- /dev/null +++ b/server/src/main.cpp @@ -0,0 +1,468 @@ +#define __STDC_FORMAT_MACROS +#include <inttypes.h> +#include <time.h> +#include <detect.h> +#include <system.h> +#include <argparse.h> +#include <json.h> +#include "server.h" +#include "main.h" + +#if defined(CONF_FAMILY_UNIX) + #include <signal.h> +#endif + +#ifndef PRId64 + #define PRId64 "I64d" +#endif + +static volatile int gs_Running = 1; +static volatile int gs_ReloadConfig = 0; + +static void ExitFunc(int Signal) +{ + printf("[EXIT] Caught signal %d\n", Signal); + gs_Running = 0; +} + +static void ReloadFunc(int Signal) +{ + printf("[RELOAD] Caught signal %d\n", Signal); + gs_ReloadConfig = 1; +} + +CConfig::CConfig() +{ + // Initialize to default values + m_Verbose = false; // -v, --verbose + str_copy(m_aConfigFile, "config.json", sizeof(m_aConfigFile)); // -c, --config + str_copy(m_aWebDir, "../web/", sizeof(m_aJSONFile)); // -d, --web-dir + str_copy(m_aTemplateFile, "template.html", sizeof(m_aTemplateFile)); + str_copy(m_aJSONFile, "json/stats.json", sizeof(m_aJSONFile)); + str_copy(m_aBindAddr, "", sizeof(m_aBindAddr)); // -b, --bind + m_Port = 35601; // -p, --port +} + +CMain::CMain(CConfig Config) : m_Config(Config) +{ + mem_zero(m_aClients, sizeof(m_aClients)); + for(int i = 0; i < NET_MAX_CLIENTS; i++) + m_aClients[i].m_ClientNetID = -1; +} + +CMain::CClient *CMain::ClientNet(int ClientNetID) +{ + if(ClientNetID < 0 || ClientNetID >= NET_MAX_CLIENTS) + return 0; + + for(int i = 0; i < NET_MAX_CLIENTS; i++) + { + if(Client(i)->m_ClientNetID == ClientNetID) + return Client(i); + } + + return 0; +} + +int CMain::ClientNetToClient(int ClientNetID) +{ + if(ClientNetID < 0 || ClientNetID >= NET_MAX_CLIENTS) + return -1; + + for(int i = 0; i < NET_MAX_CLIENTS; i++) + { + if(Client(i)->m_ClientNetID == ClientNetID) + return i; + } + + return -1; +} + +void CMain::OnNewClient(int ClientNetID, int ClientID) +{ + dbg_msg("main", "OnNewClient(ncid=%d, cid=%d)", ClientNetID, ClientID); + Client(ClientID)->m_ClientNetID = ClientNetID; + Client(ClientID)->m_ClientNetType = m_Server.Network()->ClientAddr(ClientNetID)->type; + Client(ClientID)->m_TimeConnected = time_get(); + Client(ClientID)->m_Connected = true; + + if(Client(ClientID)->m_ClientNetType == NETTYPE_IPV4) + Client(ClientID)->m_Stats.m_Online4 = true; + else if(Client(ClientID)->m_ClientNetType == NETTYPE_IPV6) + Client(ClientID)->m_Stats.m_Online6 = true; +} + +void CMain::OnDelClient(int ClientNetID) +{ + int ClientID = ClientNetToClient(ClientNetID); + dbg_msg("main", "OnDelClient(ncid=%d, cid=%d)", ClientNetID, ClientID); + if(ClientID >= 0 && ClientID < NET_MAX_CLIENTS) + { + Client(ClientID)->m_Connected = false; + Client(ClientID)->m_ClientNetID = -1; + Client(ClientID)->m_ClientNetType = NETTYPE_INVALID; + mem_zero(&Client(ClientID)->m_Stats, sizeof(CClient::CStats)); + } +} + +int CMain::HandleMessage(int ClientNetID, char *pMessage) +{ + CClient *pClient = ClientNet(ClientNetID); + if(!pClient) + return true; + + if(str_comp_num(pMessage, "update", sizeof("update")-1) == 0) + { + char *pData = str_skip_whitespaces(&pMessage[sizeof("update")-1]); + + // parse json data + json_settings JsonSettings; + mem_zero(&JsonSettings, sizeof(JsonSettings)); + char aError[256]; + json_value *pJsonData = json_parse_ex(&JsonSettings, pData, strlen(pData), aError); + if(!pJsonData) + { + dbg_msg("main", "JSON Error: %s", aError); + if(pClient->m_Stats.m_Pong) + m_Server.Network()->Send(ClientNetID, "1"); + return 1; + } + + // extract data + const json_value &rStart = (*pJsonData); + if(rStart["uptime"].type) + pClient->m_Stats.m_Uptime = rStart["uptime"].u.integer; + if(rStart["load_1"].type) + pClient->m_Stats.m_Load_1 = rStart["load_1"].u.dbl; + if(rStart["load_5"].type) + pClient->m_Stats.m_Load_5 = rStart["load_5"].u.dbl; + if(rStart["load_15"].type) + pClient->m_Stats.m_Load_15 = rStart["load_15"].u.dbl; + if(rStart["network_rx"].type) + pClient->m_Stats.m_NetworkRx = rStart["network_rx"].u.integer; + if(rStart["network_tx"].type) + pClient->m_Stats.m_NetworkTx = rStart["network_tx"].u.integer; + if(rStart["network_in"].type) + pClient->m_Stats.m_NetworkIN = rStart["network_in"].u.integer; + if(rStart["network_out"].type) + pClient->m_Stats.m_NetworkOUT = rStart["network_out"].u.integer; + if(rStart["memory_total"].type) + pClient->m_Stats.m_MemTotal = rStart["memory_total"].u.integer; + if(rStart["memory_used"].type) + pClient->m_Stats.m_MemUsed = rStart["memory_used"].u.integer; + if(rStart["swap_total"].type) + pClient->m_Stats.m_SwapTotal = rStart["swap_total"].u.integer; + if(rStart["swap_used"].type) + pClient->m_Stats.m_SwapUsed = rStart["swap_used"].u.integer; + if(rStart["hdd_total"].type) + pClient->m_Stats.m_HDDTotal = rStart["hdd_total"].u.integer; + if(rStart["hdd_used"].type) + pClient->m_Stats.m_HDDUsed = rStart["hdd_used"].u.integer; + if(rStart["cpu"].type) + pClient->m_Stats.m_CPU = rStart["cpu"].u.dbl; + if(rStart["online4"].type && pClient->m_ClientNetType == NETTYPE_IPV6) + pClient->m_Stats.m_Online4 = rStart["online4"].u.boolean; + if(rStart["online6"].type && pClient->m_ClientNetType == NETTYPE_IPV4) + pClient->m_Stats.m_Online6 = rStart["online6"].u.boolean; + if(rStart["ip_status"].type) + pClient->m_Stats.m_IpStatus = rStart["ip_status"].u.boolean; + if(rStart["custom"].type == json_string) + str_copy(pClient->m_Stats.m_aCustom, rStart["custom"].u.string.ptr, sizeof(pClient->m_Stats.m_aCustom)); + + if(m_Config.m_Verbose) + { + if(rStart["online4"].type) + dbg_msg("main", "Online4: %s\nUptime: %" PRId64 "\nIpStatus: %s\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\n", + rStart["online4"].u.boolean ? "true" : "false", + pClient->m_Stats.m_Uptime, + pClient->m_Stats.m_IpStatus ? "true" : "false", + pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU); + else if(rStart["online6"].type) + dbg_msg("main", "Online6: %s\nUptime: %" PRId64 "\nIpStatus: %s\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\n", + rStart["online6"].u.boolean ? "true" : "false", + pClient->m_Stats.m_Uptime, + pClient->m_Stats.m_IpStatus ? "true" : "false", + pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU); + else + dbg_msg("main", "Uptime: %" PRId64 "\nIpStatus: %s\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\n", + pClient->m_Stats.m_Uptime, + pClient->m_Stats.m_IpStatus ? "true" : "false", + pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU); + } + + // clean up + json_value_free(pJsonData); + + if(pClient->m_Stats.m_Pong) + m_Server.Network()->Send(ClientNetID, "0"); + return 0; + } + else if(str_comp_num(pMessage, "pong", sizeof("pong")-1) == 0) + { + char *pData = str_skip_whitespaces(&pMessage[sizeof("pong")-1]); + + if(!str_comp(pData, "0") || !str_comp(pData, "off")) + pClient->m_Stats.m_Pong = false; + else if(!str_comp(pData, "1") || !str_comp(pData, "on")) + pClient->m_Stats.m_Pong = true; + + return 0; + } + + if(pClient->m_Stats.m_Pong) + m_Server.Network()->Send(ClientNetID, "1"); + + return 1; +} + +void CMain::JSONUpdateThread(void *pUser) +{ + CJSONUpdateThreadData *m_pJSONUpdateThreadData = (CJSONUpdateThreadData *)pUser; + CClient *pClients = m_pJSONUpdateThreadData->pClients; + CConfig *pConfig = m_pJSONUpdateThreadData->pConfig; + + while(gs_Running) + { + char aFileBuf[2048*NET_MAX_CLIENTS]; + char *pBuf = aFileBuf; + + str_format(pBuf, sizeof(aFileBuf), "{\n\"servers\": [\n"); + pBuf += strlen(pBuf); + + for(int i = 0; i < NET_MAX_CLIENTS; i++) + { + if(!pClients[i].m_Active || pClients[i].m_Disabled) + continue; + + if(pClients[i].m_Connected) + { + // Uptime + char aUptime[16]; + int Days = pClients[i].m_Stats.m_Uptime/60.0/60.0/24.0; + if(Days > 0) + { + if(Days > 1) + str_format(aUptime, sizeof(aUptime), "%d 天", Days); + else + str_format(aUptime, sizeof(aUptime), "%d 天", Days); + } + else + str_format(aUptime, sizeof(aUptime), "%02d:%02d:%02d", (int)(pClients[i].m_Stats.m_Uptime/60.0/60.0), (int)((pClients[i].m_Stats.m_Uptime/60)%60), (int)((pClients[i].m_Stats.m_Uptime)%60)); + + str_format(pBuf, sizeof(aFileBuf) - (pBuf - aFileBuf), + "{ \"name\": \"%s\",\"type\": \"%s\",\"host\": \"%s\",\"location\": \"%s\",\"online4\": %s, \"online6\": %s,\"ip_status\": %s,\"uptime\": \"%s\",\"load_1\": %.2f, \"load_5\": %.2f, \"load_15\": %.2f,\"network_rx\": %" PRId64 ", \"network_tx\": %" PRId64 ", \"network_in\": %" PRId64 ", \"network_out\": %" PRId64 ", \"cpu\": %d, \"memory_total\": %" PRId64 ", \"memory_used\": %" PRId64 ", \"swap_total\": %" PRId64 ", \"swap_used\": %" PRId64 ", \"hdd_total\": %" PRId64 ", \"hdd_used\": %" PRId64 ", \"custom\": \"%s\" },\n", + pClients[i].m_aName,pClients[i].m_aType,pClients[i].m_aHost,pClients[i].m_aLocation, + pClients[i].m_Stats.m_Online4 ? "true" : "false",pClients[i].m_Stats.m_Online6 ? "true" : "false",pClients[i].m_Stats.m_IpStatus ? "true": "false", + aUptime, pClients[i].m_Stats.m_Load_1, pClients[i].m_Stats.m_Load_5, pClients[i].m_Stats.m_Load_15, pClients[i].m_Stats.m_NetworkRx, pClients[i].m_Stats.m_NetworkTx, pClients[i].m_Stats.m_NetworkIN, pClients[i].m_Stats.m_NetworkOUT, (int)pClients[i].m_Stats.m_CPU, pClients[i].m_Stats.m_MemTotal, pClients[i].m_Stats.m_MemUsed, pClients[i].m_Stats.m_SwapTotal, pClients[i].m_Stats.m_SwapUsed, pClients[i].m_Stats.m_HDDTotal, pClients[i].m_Stats.m_HDDUsed, pClients[i].m_Stats.m_aCustom); + pBuf += strlen(pBuf); + } + else + { + str_format(pBuf, sizeof(aFileBuf) - (pBuf - aFileBuf), "{ \"name\": \"%s\", \"type\": \"%s\", \"host\": \"%s\", \"location\": \"%s\", \"online4\": false, \"online6\": false },\n", + pClients[i].m_aName, pClients[i].m_aType, pClients[i].m_aHost, pClients[i].m_aLocation); + pBuf += strlen(pBuf); + } + } + if(!m_pJSONUpdateThreadData->m_ReloadRequired) + str_format(pBuf - 2, sizeof(aFileBuf) - (pBuf - aFileBuf), "\n],\n\"updated\": \"%lld\"\n}", (long long)time(/*ago*/0)); + else + { + str_format(pBuf - 2, sizeof(aFileBuf) - (pBuf - aFileBuf), "\n],\n\"updated\": \"%lld\",\n\"reload\": true\n}", (long long)time(/*ago*/0)); + m_pJSONUpdateThreadData->m_ReloadRequired--; + } + pBuf += strlen(pBuf); + + char aJSONFileTmp[1024]; + str_format(aJSONFileTmp, sizeof(aJSONFileTmp), "%s~", pConfig->m_aJSONFile); + IOHANDLE File = io_open(aJSONFileTmp, IOFLAG_WRITE); + if(!File) + { + dbg_msg("main", "Couldn't open %s", aJSONFileTmp); + exit(1); + } + io_write(File, aFileBuf, (pBuf - aFileBuf)); + io_flush(File); + io_close(File); + fs_rename(aJSONFileTmp, pConfig->m_aJSONFile); + thread_sleep(1000); + } + fs_remove(pConfig->m_aJSONFile); +} + +int CMain::ReadConfig() +{ + // read and parse config + IOHANDLE File = io_open(m_Config.m_aConfigFile, IOFLAG_READ); + if(!File) + { + dbg_msg("main", "Couldn't open %s", m_Config.m_aConfigFile); + return 1; + } + int FileSize = (int)io_length(File); + char *pFileData = (char *)mem_alloc(FileSize + 1, 1); + + io_read(File, pFileData, FileSize); + pFileData[FileSize] = 0; + io_close(File); + + // parse json data + json_settings JsonSettings; + mem_zero(&JsonSettings, sizeof(JsonSettings)); + char aError[256]; + json_value *pJsonData = json_parse_ex(&JsonSettings, pFileData, strlen(pFileData), aError); + if(!pJsonData) + { + dbg_msg("main", "JSON Error in file %s: %s", m_Config.m_aConfigFile, aError); + mem_free(pFileData); + return 1; + } + + // reset clients + for(int i = 0; i < NET_MAX_CLIENTS; i++) + { + if(!Client(i)->m_Active || !Client(i)->m_Connected) + continue; + + m_Server.Network()->Drop(Client(i)->m_ClientNetID, "Server reloading..."); + } + mem_zero(m_aClients, sizeof(m_aClients)); + for(int i = 0; i < NET_MAX_CLIENTS; i++) + m_aClients[i].m_ClientNetID = -1; + + // extract data + int ID = 0; + const json_value &rStart = (*pJsonData)["servers"]; + if(rStart.type == json_array) + { + for(unsigned i = 0; i < rStart.u.array.length; i++) + { + if(ID < 0 || ID >= NET_MAX_CLIENTS) + continue; + + Client(ID)->m_Active = true; + Client(ID)->m_Disabled = rStart[i]["disabled"].u.boolean; + str_copy(Client(ID)->m_aName, rStart[i]["name"].u.string.ptr, sizeof(Client(ID)->m_aName)); + str_copy(Client(ID)->m_aUsername, rStart[i]["username"].u.string.ptr, sizeof(Client(ID)->m_aUsername)); + str_copy(Client(ID)->m_aType, rStart[i]["type"].u.string.ptr, sizeof(Client(ID)->m_aType)); + str_copy(Client(ID)->m_aHost, rStart[i]["host"].u.string.ptr, sizeof(Client(ID)->m_aHost)); + str_copy(Client(ID)->m_aLocation, rStart[i]["location"].u.string.ptr, sizeof(Client(ID)->m_aLocation)); + str_copy(Client(ID)->m_aPassword, rStart[i]["password"].u.string.ptr, sizeof(Client(ID)->m_aPassword)); + + if(m_Config.m_Verbose) + { + if(Client(ID)->m_Disabled) + dbg_msg("main", "[#%d: Name: \"%s\", Username: \"%s\", Type: \"%s\", Host: \"%s\", Location: \"%s\", Password: \"%s\"]", + ID, Client(ID)->m_aName, Client(ID)->m_aUsername, Client(ID)->m_aType, Client(ID)->m_aHost, Client(ID)->m_aLocation, Client(ID)->m_aPassword); + else + dbg_msg("main", "#%d: Name: \"%s\", Username: \"%s\", Type: \"%s\", Host: \"%s\", Location: \"%s\", Password: \"%s\"", + ID, Client(ID)->m_aName, Client(ID)->m_aUsername, Client(ID)->m_aType, Client(ID)->m_aHost, Client(ID)->m_aLocation, Client(ID)->m_aPassword); + + } + ID++; + } + } + + // clean up + json_value_free(pJsonData); + mem_free(pFileData); + + // tell clients to reload the page + m_JSONUpdateThreadData.m_ReloadRequired = 2; + + return 0; +} + +int CMain::Run() +{ + if(m_Server.Init(this, m_Config.m_aBindAddr, m_Config.m_Port)) + return 1; + + if(ReadConfig()) + return 1; + + // Start JSON Update Thread + m_JSONUpdateThreadData.m_ReloadRequired = 2; + m_JSONUpdateThreadData.pClients = m_aClients; + m_JSONUpdateThreadData.pConfig = &m_Config; + void *LoadThread = thread_create(JSONUpdateThread, &m_JSONUpdateThreadData); + //thread_detach(LoadThread); + + while(gs_Running) + { + if(gs_ReloadConfig) + { + if(ReadConfig()) + return 1; + m_Server.NetBan()->UnbanAll(); + gs_ReloadConfig = 0; + } + + m_Server.Update(); + + // wait for incomming data + net_socket_read_wait(*m_Server.Network()->Socket(), 10); + } + + dbg_msg("server", "Closing."); + m_Server.Network()->Close(); + thread_wait(LoadThread); + + return 0; +} + +int main(int argc, const char *argv[]) +{ + int RetVal; + dbg_logger_stdout(); + + #if defined(CONF_FAMILY_UNIX) + signal(SIGINT, ExitFunc); + signal(SIGTERM, ExitFunc); + signal(SIGQUIT, ExitFunc); + signal(SIGHUP, ReloadFunc); + #endif + + char aUsage[128]; + CConfig Config; + str_format(aUsage, sizeof(aUsage), "%s [options]", argv[0]); + const char *pConfigFile = 0; + const char *pWebDir = 0; + const char *pBindAddr = 0; + + struct argparse_option aOptions[] = { + OPT_HELP(), + OPT_BOOLEAN('v', "verbose", &Config.m_Verbose, "Verbose output", 0), + OPT_STRING('c', "config", &pConfigFile, "Config file to use", 0), + OPT_STRING('d', "web-dir", &pWebDir, "Location of the web directory", 0), + OPT_STRING('b', "bind", &pBindAddr, "Bind to address", 0), + OPT_INTEGER('p', "port", &Config.m_Port, "Listen on port", 0), + OPT_END(), + }; + struct argparse Argparse; + argparse_init(&Argparse, aOptions, aUsage, 0); + argc = argparse_parse(&Argparse, argc, argv); + + if(pConfigFile) + str_copy(Config.m_aConfigFile, pConfigFile, sizeof(Config.m_aConfigFile)); + if(pWebDir) + str_copy(Config.m_aWebDir, pWebDir, sizeof(Config.m_aWebDir)); + if(pBindAddr) + str_copy(Config.m_aBindAddr, pBindAddr, sizeof(Config.m_aBindAddr)); + + if(Config.m_aWebDir[strlen(Config.m_aWebDir)-1] != '/') + str_append(Config.m_aWebDir, "/", sizeof(Config.m_aWebDir)); + if(!fs_is_dir(Config.m_aWebDir)) + { + dbg_msg("main", "ERROR: Can't find web directory: %s", Config.m_aWebDir); + return 1; + } + + char aTmp[1024]; + str_format(aTmp, sizeof(aTmp), "%s%s", Config.m_aWebDir, Config.m_aJSONFile); + str_copy(Config.m_aJSONFile, aTmp, sizeof(Config.m_aJSONFile)); + + CMain Main(Config); + RetVal = Main.Run(); + + return RetVal; +} diff --git a/server/src/main.h b/server/src/main.h new file mode 100644 index 0000000..a3b95cb --- /dev/null +++ b/server/src/main.h @@ -0,0 +1,93 @@ +#ifndef MAIN_H +#define MAIN_H + +#include <stdint.h> +#include "server.h" + +class CConfig +{ +public: + bool m_Verbose; + char m_aConfigFile[1024]; + char m_aWebDir[1024]; + char m_aTemplateFile[1024]; + char m_aJSONFile[1024]; + char m_aBindAddr[256]; + int m_Port; + + CConfig(); +}; + +class CMain +{ + CConfig m_Config; + CServer m_Server; + + struct CClient + { + bool m_Active; + bool m_Disabled; + bool m_Connected; + int m_ClientNetID; + int m_ClientNetType; + char m_aUsername[128]; + char m_aName[128]; + char m_aType[128]; + char m_aHost[128]; + char m_aLocation[128]; + char m_aPassword[128]; + + int64 m_TimeConnected; + int64 m_LastUpdate; + + struct CStats + { + bool m_Online4; + bool m_Online6; + bool m_IpStatus; //mh361 or mh370, mourn mh370, 2014-03-0 01:20 lost from all over the world. + int64_t m_Uptime; + double m_Load_1; //1 minutes load average + double m_Load_5; //5 minutes load average + double m_Load_15; //15 minutes load average + int64_t m_NetworkRx; + int64_t m_NetworkTx; + int64_t m_NetworkIN; + int64_t m_NetworkOUT; + int64_t m_MemTotal; + int64_t m_MemUsed; + int64_t m_SwapTotal; + int64_t m_SwapUsed; + int64_t m_HDDTotal; + int64_t m_HDDUsed; + double m_CPU; + char m_aCustom[512]; + // Options + bool m_Pong; + } m_Stats; + } m_aClients[NET_MAX_CLIENTS]; + + struct CJSONUpdateThreadData + { + CClient *pClients; + CConfig *pConfig; + volatile short m_ReloadRequired; + } m_JSONUpdateThreadData; + + static void JSONUpdateThread(void *pUser); +public: + CMain(CConfig Config); + + void OnNewClient(int ClienNettID, int ClientID); + void OnDelClient(int ClientNetID); + int HandleMessage(int ClientNetID, char *pMessage); + int ReadConfig(); + int Run(); + + CClient *Client(int ClientID) { return &m_aClients[ClientID]; } + CClient *ClientNet(int ClientNetID); + const CConfig *Config() const { return &m_Config; } + int ClientNetToClient(int ClientNetID); +}; + + +#endif diff --git a/server/src/netban.cpp b/server/src/netban.cpp new file mode 100644 index 0000000..0ed2d65 --- /dev/null +++ b/server/src/netban.cpp @@ -0,0 +1,463 @@ +#include <math.h> +#include "netban.h" + +bool CNetBan::StrAllnum(const char *pStr) +{ + while(*pStr) + { + if(!(*pStr >= '0' && *pStr <= '9')) + return false; + pStr++; + } + return true; +} + + +CNetBan::CNetHash::CNetHash(const NETADDR *pAddr) +{ + if(pAddr->type==NETTYPE_IPV4) + m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3])&0xFF; + else + m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3]+pAddr->ip[4]+pAddr->ip[5]+pAddr->ip[6]+pAddr->ip[7]+ + pAddr->ip[8]+pAddr->ip[9]+pAddr->ip[10]+pAddr->ip[11]+pAddr->ip[12]+pAddr->ip[13]+pAddr->ip[14]+pAddr->ip[15])&0xFF; + m_HashIndex = 0; +} + +CNetBan::CNetHash::CNetHash(const CNetRange *pRange) +{ + m_Hash = 0; + m_HashIndex = 0; + for(int i = 0; pRange->m_LB.ip[i] == pRange->m_UB.ip[i]; ++i) + { + m_Hash += pRange->m_LB.ip[i]; + ++m_HashIndex; + } + m_Hash &= 0xFF; +} + +int CNetBan::CNetHash::MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]) +{ + int Length = pAddr->type==NETTYPE_IPV4 ? 4 : 16; + aHash[0].m_Hash = 0; + aHash[0].m_HashIndex = 0; + for(int i = 1, Sum = 0; i <= Length; ++i) + { + Sum += pAddr->ip[i-1]; + aHash[i].m_Hash = Sum&0xFF; + aHash[i].m_HashIndex = i%Length; + } + return Length; +} + + +template<class T, int HashCount> +typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Add(const T *pData, const CBanInfo *pInfo, const CNetHash *pNetHash) +{ + if(!m_pFirstFree) + return 0; + + // create new ban + CBan<T> *pBan = m_pFirstFree; + pBan->m_Data = *pData; + pBan->m_Info = *pInfo; + pBan->m_NetHash = *pNetHash; + if(pBan->m_pNext) + pBan->m_pNext->m_pPrev = pBan->m_pPrev; + if(pBan->m_pPrev) + pBan->m_pPrev->m_pNext = pBan->m_pNext; + else + m_pFirstFree = pBan->m_pNext; + + // add it to the hash list + if(m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]) + m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]->m_pHashPrev = pBan; + pBan->m_pHashPrev = 0; + pBan->m_pHashNext = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; + m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash] = pBan; + + // insert it into the used list + if(m_pFirstUsed) + { + for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext) + { + if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires)) + { + // insert before + pBan->m_pNext = p; + pBan->m_pPrev = p->m_pPrev; + if(p->m_pPrev) + p->m_pPrev->m_pNext = pBan; + else + m_pFirstUsed = pBan; + p->m_pPrev = pBan; + break; + } + + if(!p->m_pNext) + { + // last entry + p->m_pNext = pBan; + pBan->m_pPrev = p; + pBan->m_pNext = 0; + break; + } + } + } + else + { + m_pFirstUsed = pBan; + pBan->m_pNext = pBan->m_pPrev = 0; + } + + // update ban count + ++m_CountUsed; + + return pBan; +} + +template<class T, int HashCount> +int CNetBan::CBanPool<T, HashCount>::Remove(CBan<T> *pBan) +{ + if(pBan == 0) + return -1; + + // remove from hash list + if(pBan->m_pHashNext) + pBan->m_pHashNext->m_pHashPrev = pBan->m_pHashPrev; + if(pBan->m_pHashPrev) + pBan->m_pHashPrev->m_pHashNext = pBan->m_pHashNext; + else + m_paaHashList[pBan->m_NetHash.m_HashIndex][pBan->m_NetHash.m_Hash] = pBan->m_pHashNext; + pBan->m_pHashNext = pBan->m_pHashPrev = 0; + + // remove from used list + if(pBan->m_pNext) + pBan->m_pNext->m_pPrev = pBan->m_pPrev; + if(pBan->m_pPrev) + pBan->m_pPrev->m_pNext = pBan->m_pNext; + else + m_pFirstUsed = pBan->m_pNext; + + // add to recycle list + if(m_pFirstFree) + m_pFirstFree->m_pPrev = pBan; + pBan->m_pPrev = 0; + pBan->m_pNext = m_pFirstFree; + m_pFirstFree = pBan; + + // update ban count + --m_CountUsed; + + return 0; +} + +template<class T, int HashCount> +void CNetBan::CBanPool<T, HashCount>::Update(CBan<CDataType> *pBan, const CBanInfo *pInfo) +{ + pBan->m_Info = *pInfo; + + // remove from used list + if(pBan->m_pNext) + pBan->m_pNext->m_pPrev = pBan->m_pPrev; + if(pBan->m_pPrev) + pBan->m_pPrev->m_pNext = pBan->m_pNext; + else + m_pFirstUsed = pBan->m_pNext; + + // insert it into the used list + if(m_pFirstUsed) + { + for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext) + { + if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires)) + { + // insert before + pBan->m_pNext = p; + pBan->m_pPrev = p->m_pPrev; + if(p->m_pPrev) + p->m_pPrev->m_pNext = pBan; + else + m_pFirstUsed = pBan; + p->m_pPrev = pBan; + break; + } + + if(!p->m_pNext) + { + // last entry + p->m_pNext = pBan; + pBan->m_pPrev = p; + pBan->m_pNext = 0; + break; + } + } + } + else + { + m_pFirstUsed = pBan; + pBan->m_pNext = pBan->m_pPrev = 0; + } +} + +template<class T, int HashCount> +void CNetBan::CBanPool<T, HashCount>::Reset() +{ + mem_zero(m_paaHashList, sizeof(m_paaHashList)); + mem_zero(m_aBans, sizeof(m_aBans)); + m_pFirstUsed = 0; + m_CountUsed = 0; + + for(int i = 1; i < MAX_BANS-1; ++i) + { + m_aBans[i].m_pNext = &m_aBans[i+1]; + m_aBans[i].m_pPrev = &m_aBans[i-1]; + } + + m_aBans[0].m_pNext = &m_aBans[1]; + m_aBans[MAX_BANS-1].m_pPrev = &m_aBans[MAX_BANS-2]; + m_pFirstFree = &m_aBans[0]; +} + +template<class T, int HashCount> +typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Get(int Index) const +{ + if(Index < 0 || Index >= Num()) + return 0; + + for(CNetBan::CBan<T> *pBan = m_pFirstUsed; pBan; pBan = pBan->m_pNext, --Index) + { + if(Index == 0) + return pBan; + } + + return 0; +} + + +template<class T> +void CNetBan::MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const +{ + if(pBan == 0 || pBuf == 0) + { + if(BuffSize > 0) + pBuf[0] = 0; + return; + } + + // build type based part + char aBuf[256]; + if(Type == MSGTYPE_PLAYER) + str_copy(aBuf, "You have been banned", sizeof(aBuf)); + else + { + char aTemp[256]; + switch(Type) + { + case MSGTYPE_LIST: + str_format(aBuf, sizeof(aBuf), "%s banned", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break; + case MSGTYPE_BANADD: + str_format(aBuf, sizeof(aBuf), "banned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break; + case MSGTYPE_BANREM: + str_format(aBuf, sizeof(aBuf), "unbanned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break; + default: + aBuf[0] = 0; + } + } + + // add info part + if(pBan->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER) + { + int Mins = ((pBan->m_Info.m_Expires-time_timestamp()) + 59) / 60; + if(Mins <= 1) + str_format(pBuf, BuffSize, "%s for 1 minute (%s)", aBuf, pBan->m_Info.m_aReason); + else + str_format(pBuf, BuffSize, "%s for %d minutes (%s)", aBuf, Mins, pBan->m_Info.m_aReason); + } + else + str_format(pBuf, BuffSize, "%s for life (%s)", aBuf, pBan->m_Info.m_aReason); +} + +template<class T> +int CNetBan::Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason) +{ + // do not ban localhost + if(NetMatch(pData, &m_LocalhostIPV4) || NetMatch(pData, &m_LocalhostIPV6)) + { + dbg_msg("net_ban", "ban failed (localhost)"); + return -1; + } + + int Stamp = Seconds > 0 ? time_timestamp()+Seconds : CBanInfo::EXPIRES_NEVER; + + // set up info + CBanInfo Info = {0}; + Info.m_Expires = Stamp; + str_copy(Info.m_aReason, pReason, sizeof(Info.m_aReason)); + + // check if it already exists + CNetHash NetHash(pData); + CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash); + if(pBan) + { + // adjust the ban + pBanPool->Update(pBan, &Info); + char aBuf[128]; + MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST); + dbg_msg("net_ban", aBuf); + return 1; + } + + // add ban and print result + pBan = pBanPool->Add(pData, &Info, &NetHash); + if(pBan) + { + char aBuf[128]; + MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANADD); + dbg_msg("net_ban", aBuf); + return 0; + } + else + dbg_msg("net_ban", "ban failed (full banlist)"); + return -1; +} + +template<class T> +int CNetBan::Unban(T *pBanPool, const typename T::CDataType *pData) +{ + CNetHash NetHash(pData); + CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash); + if(pBan) + { + char aBuf[256]; + MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANREM); + pBanPool->Remove(pBan); + dbg_msg("net_ban", aBuf); + return 0; + } + else + dbg_msg("net_ban", "unban failed (invalid entry)"); + return -1; +} + +void CNetBan::Init() +{ + m_BanAddrPool.Reset(); + m_BanRangePool.Reset(); + + net_host_lookup("localhost", &m_LocalhostIPV4, NETTYPE_IPV4); + net_host_lookup("localhost", &m_LocalhostIPV6, NETTYPE_IPV6); +} + +void CNetBan::Update() +{ + int Now = time_timestamp(); + + // remove expired bans + char aBuf[256], aNetStr[256]; + while(m_BanAddrPool.First() && m_BanAddrPool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanAddrPool.First()->m_Info.m_Expires < Now) + { + str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanAddrPool.First()->m_Data, aNetStr, sizeof(aNetStr))); + dbg_msg("net_ban", aBuf); + m_BanAddrPool.Remove(m_BanAddrPool.First()); + } + while(m_BanRangePool.First() && m_BanRangePool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanRangePool.First()->m_Info.m_Expires < Now) + { + str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanRangePool.First()->m_Data, aNetStr, sizeof(aNetStr))); + dbg_msg("net_ban", aBuf); + m_BanRangePool.Remove(m_BanRangePool.First()); + } +} + +int CNetBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason) +{ + return Ban(&m_BanAddrPool, pAddr, Seconds, pReason); +} + +int CNetBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason) +{ + if(pRange->IsValid()) + return Ban(&m_BanRangePool, pRange, Seconds, pReason); + + dbg_msg("net_ban", "ban failed (invalid range)"); + return -1; +} + +int CNetBan::UnbanByAddr(const NETADDR *pAddr) +{ + return Unban(&m_BanAddrPool, pAddr); +} + +int CNetBan::UnbanByRange(const CNetRange *pRange) +{ + if(pRange->IsValid()) + return Unban(&m_BanRangePool, pRange); + + dbg_msg("net_ban", "ban failed (invalid range)"); + return -1; +} + +int CNetBan::UnbanByIndex(int Index) +{ + int Result; + char aBuf[256]; + CBanAddr *pBan = m_BanAddrPool.Get(Index); + if(pBan) + { + NetToString(&pBan->m_Data, aBuf, sizeof(aBuf)); + Result = m_BanAddrPool.Remove(pBan); + } + else + { + CBanRange *pBan = m_BanRangePool.Get(Index-m_BanAddrPool.Num()); + if(pBan) + { + NetToString(&pBan->m_Data, aBuf, sizeof(aBuf)); + Result = m_BanRangePool.Remove(pBan); + } + else + { + dbg_msg("net_ban", "unban failed (invalid index)"); + return -1; + } + } + + char aMsg[256]; + str_format(aMsg, sizeof(aMsg), "unbanned index %i (%s)", Index, aBuf); + dbg_msg("net_ban", aMsg); + return Result; +} + +void CNetBan::UnbanAll() +{ + m_BanAddrPool.Reset(); + m_BanRangePool.Reset(); +} + +bool CNetBan::IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const +{ + CNetHash aHash[17]; + int Length = CNetHash::MakeHashArray(pAddr, aHash); + + // check ban adresses + CBanAddr *pBan = m_BanAddrPool.Find(pAddr, &aHash[Length]); + if(pBan) + { + MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER); + return true; + } + + // check ban ranges + for(int i = Length-1; i >= 0; --i) + { + for(CBanRange *pBan = m_BanRangePool.First(&aHash[i]); pBan; pBan = pBan->m_pHashNext) + { + if(NetMatch(&pBan->m_Data, pAddr, i, Length)) + { + MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER); + return true; + } + } + } + + return false; +} diff --git a/server/src/netban.h b/server/src/netban.h new file mode 100644 index 0000000..544a4b1 --- /dev/null +++ b/server/src/netban.h @@ -0,0 +1,179 @@ +#ifndef NETBAN_H +#define NETBAN_H + +#include <system.h> + +inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2) +{ + return mem_comp(pAddr1, pAddr2, pAddr1->type==NETTYPE_IPV4 ? 8 : 20); +} + +class CNetRange +{ +public: + NETADDR m_LB; + NETADDR m_UB; + + bool IsValid() const { return m_LB.type == m_UB.type && NetComp(&m_LB, &m_UB) < 0; } +}; + +inline int NetComp(const CNetRange *pRange1, const CNetRange *pRange2) +{ + return NetComp(&pRange1->m_LB, &pRange2->m_LB) || NetComp(&pRange1->m_UB, &pRange2->m_UB); +} + + +class CNetBan +{ +protected: + bool NetMatch(const NETADDR *pAddr1, const NETADDR *pAddr2) const + { + return NetComp(pAddr1, pAddr2) == 0; + } + + bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr, int Start, int Length) const + { + return pRange->m_LB.type == pAddr->type && (Start == 0 || mem_comp(&pRange->m_LB.ip[0], &pAddr->ip[0], Start) == 0) && + mem_comp(&pRange->m_LB.ip[Start], &pAddr->ip[Start], Length-Start) <= 0 && mem_comp(&pRange->m_UB.ip[Start], &pAddr->ip[Start], Length-Start) >= 0; + } + + bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr) const + { + return NetMatch(pRange, pAddr, 0, pRange->m_LB.type==NETTYPE_IPV4 ? 4 : 16); + } + + const char *NetToString(const NETADDR *pData, char *pBuffer, unsigned BufferSize) const + { + char aAddrStr[NETADDR_MAXSTRSIZE]; + net_addr_str(pData, aAddrStr, sizeof(aAddrStr), false); + str_format(pBuffer, BufferSize, "'%s'", aAddrStr); + return pBuffer; + } + + const char *NetToString(const CNetRange *pData, char *pBuffer, unsigned BufferSize) const + { + char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE]; + net_addr_str(&pData->m_LB, aAddrStr1, sizeof(aAddrStr1), false); + net_addr_str(&pData->m_UB, aAddrStr2, sizeof(aAddrStr2), false); + str_format(pBuffer, BufferSize, "'%s' - '%s'", aAddrStr1, aAddrStr2); + return pBuffer; + } + + // todo: move? + static bool StrAllnum(const char *pStr); + + class CNetHash + { + public: + int m_Hash; + int m_HashIndex; // matching parts for ranges, 0 for addr + + CNetHash() {} + CNetHash(const NETADDR *pAddr); + CNetHash(const CNetRange *pRange); + + static int MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]); + }; + + struct CBanInfo + { + enum + { + EXPIRES_NEVER=-1, + REASON_LENGTH=64, + }; + int m_Expires; + char m_aReason[REASON_LENGTH]; + }; + + template<class T> struct CBan + { + T m_Data; + CBanInfo m_Info; + CNetHash m_NetHash; + + // hash list + CBan *m_pHashNext; + CBan *m_pHashPrev; + + // used or free list + CBan *m_pNext; + CBan *m_pPrev; + }; + + template<class T, int HashCount> class CBanPool + { + public: + typedef T CDataType; + + CBan<CDataType> *Add(const CDataType *pData, const CBanInfo *pInfo, const CNetHash *pNetHash); + int Remove(CBan<CDataType> *pBan); + void Update(CBan<CDataType> *pBan, const CBanInfo *pInfo); + void Reset(); + + int Num() const { return m_CountUsed; } + bool IsFull() const { return m_CountUsed == MAX_BANS; } + + CBan<CDataType> *First() const { return m_pFirstUsed; } + CBan<CDataType> *First(const CNetHash *pNetHash) const { return m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; } + CBan<CDataType> *Find(const CDataType *pData, const CNetHash *pNetHash) const + { + for(CBan<CDataType> *pBan = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; pBan; pBan = pBan->m_pHashNext) + { + if(NetComp(&pBan->m_Data, pData) == 0) + return pBan; + } + + return 0; + } + CBan<CDataType> *Get(int Index) const; + + private: + enum + { + MAX_BANS=1024, + }; + + CBan<CDataType> *m_paaHashList[HashCount][256]; + CBan<CDataType> m_aBans[MAX_BANS]; + CBan<CDataType> *m_pFirstFree; + CBan<CDataType> *m_pFirstUsed; + int m_CountUsed; + }; + + typedef CBanPool<NETADDR, 1> CBanAddrPool; + typedef CBanPool<CNetRange, 16> CBanRangePool; + typedef CBan<NETADDR> CBanAddr; + typedef CBan<CNetRange> CBanRange; + + template<class T> void MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const; + template<class T> int Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason); + template<class T> int Unban(T *pBanPool, const typename T::CDataType *pData); + + CBanAddrPool m_BanAddrPool; + CBanRangePool m_BanRangePool; + NETADDR m_LocalhostIPV4, m_LocalhostIPV6; + +public: + enum + { + MSGTYPE_PLAYER=0, + MSGTYPE_LIST, + MSGTYPE_BANADD, + MSGTYPE_BANREM, + }; + + virtual ~CNetBan() {} + void Init(); + void Update(); + + virtual int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason); + virtual int BanRange(const CNetRange *pRange, int Seconds, const char *pReason); + int UnbanByAddr(const NETADDR *pAddr); + int UnbanByRange(const CNetRange *pRange); + int UnbanByIndex(int Index); + void UnbanAll(); + bool IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const; +}; + +#endif diff --git a/server/src/network.cpp b/server/src/network.cpp new file mode 100644 index 0000000..a1cb1ae --- /dev/null +++ b/server/src/network.cpp @@ -0,0 +1,144 @@ +#include <system.h> +#include "netban.h" +#include "network.h" + +bool CNetwork::Open(NETADDR BindAddr, CNetBan *pNetBan) +{ + // zero out the whole structure + mem_zero(this, sizeof(*this)); + m_Socket.type = NETTYPE_INVALID; + m_Socket.ipv4sock = -1; + m_Socket.ipv6sock = -1; + m_pNetBan = pNetBan; + + // open socket + m_Socket = net_tcp_create(BindAddr); + if(!m_Socket.type) + return false; + if(net_tcp_listen(m_Socket, NET_MAX_CLIENTS)) + return false; + net_set_non_blocking(m_Socket); + + for(int i = 0; i < NET_MAX_CLIENTS; i++) + m_aSlots[i].m_Connection.Reset(); + + return true; +} + +void CNetwork::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser) +{ + m_pfnNewClient = pfnNewClient; + m_pfnDelClient = pfnDelClient; + m_UserPtr = pUser; +} + +int CNetwork::Close() +{ + for(int i = 0; i < NET_MAX_CLIENTS; i++) + m_aSlots[i].m_Connection.Disconnect("Closing connection."); + + net_tcp_close(m_Socket); + + return 0; +} + +int CNetwork::Drop(int ClientID, const char *pReason) +{ + if(m_pfnDelClient) + m_pfnDelClient(ClientID, pReason, m_UserPtr); + + m_aSlots[ClientID].m_Connection.Disconnect(pReason); + + return 0; +} + +int CNetwork::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr) +{ + char aError[256] = { 0 }; + int FreeSlot = -1; + + // look for free slot or multiple client + for(int i = 0; i < NET_MAX_CLIENTS; i++) + { + if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) + FreeSlot = i; + if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE) + { + if(net_addr_comp(pAddr, m_aSlots[i].m_Connection.PeerAddress()) == 0) + { + str_copy(aError, "Only one client per IP allowed.", sizeof(aError)); + break; + } + } + } + + // accept client + if(!aError[0] && FreeSlot != -1) + { + m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr); + if(m_pfnNewClient) + m_pfnNewClient(FreeSlot, m_UserPtr); + return 0; + } + + // reject client + if(!aError[0]) + str_copy(aError, "No free slot available.", sizeof(aError)); + + net_tcp_send(Socket, aError, str_length(aError)); + net_tcp_close(Socket); + + return -1; +} + +int CNetwork::Update() +{ + NETSOCKET Socket; + NETADDR Addr; + + if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0) + { + // check if we should just drop the packet + char aBuf[128]; + if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf))) + { + // banned, reply with a message and drop + net_tcp_send(Socket, aBuf, str_length(aBuf)); + net_tcp_close(Socket); + } + else + AcceptClient(Socket, &Addr); + } + + for(int i = 0; i < NET_MAX_CLIENTS; i++) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE) + m_aSlots[i].m_Connection.Update(); + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR) + Drop(i, m_aSlots[i].m_Connection.ErrorString()); + } + + return 0; +} + +int CNetwork::Recv(char *pLine, int MaxLength, int *pClientID) +{ + for(int i = 0; i < NET_MAX_CLIENTS; i++) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE && m_aSlots[i].m_Connection.Recv(pLine, MaxLength)) + { + if(pClientID) + *pClientID = i; + return 1; + } + } + return 0; +} + +int CNetwork::Send(int ClientID, const char *pLine) +{ + if(m_aSlots[ClientID].m_Connection.State() == NET_CONNSTATE_ONLINE) + return m_aSlots[ClientID].m_Connection.Send(pLine); + else + return -1; +} diff --git a/server/src/network.h b/server/src/network.h new file mode 100644 index 0000000..5dc2396 --- /dev/null +++ b/server/src/network.h @@ -0,0 +1,87 @@ +#ifndef NETWORK_H +#define NETWORK_H + +enum +{ + NET_CONNSTATE_OFFLINE=0, + NET_CONNSTATE_CONNECT=1, + NET_CONNSTATE_PENDING=2, + NET_CONNSTATE_ONLINE=3, + NET_CONNSTATE_ERROR=4, + + NET_MAX_PACKETSIZE = 1400, + NET_MAX_CLIENTS = 128 +}; + +typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char* pReason, void *pUser); +typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser); + +class CNetworkClient +{ +private: + int m_State; + + NETADDR m_PeerAddr; + NETSOCKET m_Socket; + + char m_aBuffer[NET_MAX_PACKETSIZE]; + int m_BufferOffset; + + char m_aErrorString[256]; + + bool m_LineEndingDetected; + char m_aLineEnding[3]; + +public: + void Init(NETSOCKET Socket, const NETADDR *pAddr); + void Disconnect(const char *pReason); + + int State() const { return m_State; } + const NETADDR *PeerAddress() const { return &m_PeerAddr; } + const char *ErrorString() const { return m_aErrorString; } + + void Reset(); + int Update(); + int Send(const char *pLine); + int Recv(char *pLine, int MaxLength); +}; + +class CNetwork +{ +private: + struct CSlot + { + CNetworkClient m_Connection; + }; + + NETSOCKET m_Socket; + class CNetBan *m_pNetBan; + CSlot m_aSlots[NET_MAX_CLIENTS]; + + NETFUNC_NEWCLIENT m_pfnNewClient; + NETFUNC_DELCLIENT m_pfnDelClient; + void *m_UserPtr; + +public: + void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser); + + // + bool Open(NETADDR BindAddr, CNetBan *pNetBan); + int Close(); + + // + int Recv(char *pLine, int MaxLength, int *pClientID = 0); + int Send(int ClientID, const char *pLine); + int Update(); + + // + int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr); + int Drop(int ClientID, const char *pReason); + + // status requests + const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } + const NETSOCKET *Socket() const { return &m_Socket; } + class CNetBan *NetBan() const { return m_pNetBan; } +}; + +#endif diff --git a/server/src/network_client.cpp b/server/src/network_client.cpp new file mode 100644 index 0000000..0a4bce5 --- /dev/null +++ b/server/src/network_client.cpp @@ -0,0 +1,184 @@ +#include <system.h> +#include "network.h" + +void CNetworkClient::Reset() +{ + m_State = NET_CONNSTATE_OFFLINE; + mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); + m_aErrorString[0] = 0; + + m_Socket.type = NETTYPE_INVALID; + m_Socket.ipv4sock = -1; + m_Socket.ipv6sock = -1; + m_aBuffer[0] = 0; + m_BufferOffset = 0; + + m_LineEndingDetected = false; + #if defined(CONF_FAMILY_WINDOWS) + m_aLineEnding[0] = '\r'; + m_aLineEnding[1] = '\n'; + m_aLineEnding[2] = 0; + #else + m_aLineEnding[0] = '\n'; + m_aLineEnding[1] = 0; + m_aLineEnding[2] = 0; + #endif +} + +void CNetworkClient::Init(NETSOCKET Socket, const NETADDR *pAddr) +{ + Reset(); + + m_Socket = Socket; + net_set_non_blocking(m_Socket); + + m_PeerAddr = *pAddr; + m_State = NET_CONNSTATE_ONLINE; +} + +void CNetworkClient::Disconnect(const char *pReason) +{ + if(State() == NET_CONNSTATE_OFFLINE) + return; + + if(pReason && pReason[0]) + Send(pReason); + + net_tcp_close(m_Socket); + + Reset(); +} + +int CNetworkClient::Update() +{ + if(State() == NET_CONNSTATE_ONLINE) + { + if((int)(sizeof(m_aBuffer)) <= m_BufferOffset) + { + m_State = NET_CONNSTATE_ERROR; + str_copy(m_aErrorString, "too weak connection (out of buffer)", sizeof(m_aErrorString)); + return -1; + } + + int Bytes = net_tcp_recv(m_Socket, m_aBuffer+m_BufferOffset, (int)(sizeof(m_aBuffer))-m_BufferOffset); + + if(Bytes > 0) + { + m_BufferOffset += Bytes; + } + else if(Bytes < 0) + { + if(net_would_block()) // no data received + return 0; + + m_State = NET_CONNSTATE_ERROR; // error + str_copy(m_aErrorString, "connection failure", sizeof(m_aErrorString)); + return -1; + } + else + { + m_State = NET_CONNSTATE_ERROR; + str_copy(m_aErrorString, "remote end closed the connection", sizeof(m_aErrorString)); + return -1; + } + } + + return 0; +} + +int CNetworkClient::Recv(char *pLine, int MaxLength) +{ + if(State() == NET_CONNSTATE_ONLINE) + { + if(m_BufferOffset) + { + // find message start + int StartOffset = 0; + while(m_aBuffer[StartOffset] == '\r' || m_aBuffer[StartOffset] == '\n') + { + // detect clients line ending format + if(!m_LineEndingDetected) + { + m_aLineEnding[0] = m_aBuffer[StartOffset]; + if(StartOffset+1 < m_BufferOffset && (m_aBuffer[StartOffset+1] == '\r' || m_aBuffer[StartOffset+1] == '\n') && + m_aBuffer[StartOffset] != m_aBuffer[StartOffset+1]) + m_aLineEnding[1] = m_aBuffer[StartOffset+1]; + m_LineEndingDetected = true; + } + + if(++StartOffset >= m_BufferOffset) + { + m_BufferOffset = 0; + return 0; + } + } + + // find message end + int EndOffset = StartOffset; + while(m_aBuffer[EndOffset] != '\r' && m_aBuffer[EndOffset] != '\n') + { + if(++EndOffset >= m_BufferOffset) + { + if(StartOffset > 0) + { + mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset); + m_BufferOffset -= StartOffset; + } + return 0; + } + } + + // extract message and update buffer + if(MaxLength-1 < EndOffset-StartOffset) + { + if(StartOffset > 0) + { + mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset); + m_BufferOffset -= StartOffset; + } + return 0; + } + mem_copy(pLine, m_aBuffer+StartOffset, EndOffset-StartOffset); + pLine[EndOffset-StartOffset] = 0; + str_sanitize_cc(pLine); + mem_move(m_aBuffer, m_aBuffer+EndOffset, m_BufferOffset-EndOffset); + m_BufferOffset -= EndOffset; + return 1; + } + } + return 0; +} + +int CNetworkClient::Send(const char *pLine) +{ + if(State() != NET_CONNSTATE_ONLINE) + return -1; + + char aBuf[1024]; + str_copy(aBuf, pLine, (int)(sizeof(aBuf))-2); + int Length = str_length(aBuf); + aBuf[Length] = m_aLineEnding[0]; + aBuf[Length+1] = m_aLineEnding[1]; + aBuf[Length+2] = m_aLineEnding[2]; + Length += 3; + const char *pData = aBuf; + + while(1) + { + int Send = net_tcp_send(m_Socket, pData, Length); + if(Send < 0) + { + m_State = NET_CONNSTATE_ERROR; + str_copy(m_aErrorString, "failed to send packet", sizeof(m_aErrorString)); + return -1; + } + + if(Send >= Length) + break; + + pData += Send; + Length -= Send; + } + + return 0; +} diff --git a/server/src/server.cpp b/server/src/server.cpp new file mode 100644 index 0000000..6996ac3 --- /dev/null +++ b/server/src/server.cpp @@ -0,0 +1,204 @@ +#include <system.h> +#include "netban.h" +#include "network.h" +#include "main.h" +#include "server.h" + +int CServer::NewClientCallback(int ClientID, void *pUser) +{ + CServer *pThis = (CServer *)pUser; + + char aAddrStr[NETADDR_MAXSTRSIZE]; + net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true); + if(pThis->Main()->Config()->m_Verbose) + dbg_msg("server", "Connection accepted. ncid=%d addr=%s'", ClientID, aAddrStr); + + pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTED; + pThis->m_aClients[ClientID].m_TimeConnected = time_get(); + pThis->m_Network.Send(ClientID, "Authentication required:"); + + return 0; +} + +int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser) +{ + CServer *pThis = (CServer *)pUser; + + char aAddrStr[NETADDR_MAXSTRSIZE]; + net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true); + if(pThis->Main()->Config()->m_Verbose) + dbg_msg("server", "Client dropped. ncid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason); + + if(pThis->m_aClients[ClientID].m_State == CClient::STATE_AUTHED) + pThis->Main()->OnDelClient(ClientID); + pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY; + + return 0; +} + +int CServer::Init(CMain *pMain, const char *Bind, int Port) +{ + m_pMain = pMain; + m_NetBan.Init(); + + for(int i = 0; i < NET_MAX_CLIENTS; i++) + m_aClients[i].m_State = CClient::STATE_EMPTY; + + m_Ready = false; + + if(Port == 0) + { + dbg_msg("server", "Will not bind to port 0."); + return 1; + } + + NETADDR BindAddr; + if(Bind[0] && net_host_lookup(Bind, &BindAddr, NETTYPE_ALL) == 0) + { + // got bindaddr + BindAddr.type = NETTYPE_ALL; + BindAddr.port = Port; + } + else + { + mem_zero(&BindAddr, sizeof(BindAddr)); + BindAddr.type = NETTYPE_ALL; + BindAddr.port = Port; + } + + if(m_Network.Open(BindAddr, &m_NetBan)) + { + m_Network.SetCallbacks(NewClientCallback, DelClientCallback, this); + m_Ready = true; + dbg_msg("server", "Bound to %s:%d", Bind, Port); + return 0; + } + else + dbg_msg("server", "Couldn't open socket. Port (%d) might already be in use.", Port); + + return 1; +} + +void CServer::Update() +{ + if(!m_Ready) + return; + + m_NetBan.Update(); + m_Network.Update(); + + char aBuf[NET_MAX_PACKETSIZE]; + int ClientID; + + while(m_Network.Recv(aBuf, (int)(sizeof(aBuf))-1, &ClientID)) + { + dbg_assert(m_aClients[ClientID].m_State != CClient::STATE_EMPTY, "Got message from empty slot."); + if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTED) + { + int ID = -1; + char aUsername[128] = {0}; + char aPassword[128] = {0}; + const char *pTmp; + + if(!(pTmp = str_find(aBuf, ":")) + || (unsigned)(pTmp - aBuf) > sizeof(aUsername) || (unsigned)(str_length(pTmp) - 1) > sizeof(aPassword)) + { + m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away."); + m_Network.Drop(ClientID, "Fuck off."); + return; + } + + str_copy(aUsername, aBuf, pTmp - aBuf + 1); + str_copy(aPassword, pTmp + 1, sizeof(aPassword)); + if(!*aUsername || !*aPassword) + { + m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away."); + m_Network.Drop(ClientID, "Username and password must not be blank."); + return; + } + + for(int i = 0; i < NET_MAX_CLIENTS; i++) + { + if(!Main()->Client(i)->m_Active) + continue; + + if(str_comp(Main()->Client(i)->m_aUsername, aUsername) == 0 && str_comp(Main()->Client(i)->m_aPassword, aPassword) == 0) + ID = i; + } + + if(ID == -1) + { + m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "Wrong username and/or password."); + m_Network.Drop(ClientID, "Wrong username and/or password."); + } + else if(Main()->Client(ID)->m_ClientNetID != -1) + { + m_Network.Drop(ClientID, "Only one connection per user allowed."); + } + else + { + m_aClients[ClientID].m_State = CClient::STATE_AUTHED; + m_aClients[ClientID].m_LastReceived = time_get(); + m_Network.Send(ClientID, "Authentication successful. Access granted."); + + if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV4) + m_Network.Send(ClientID, "You are connecting via: IPv4"); + else if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV6) + m_Network.Send(ClientID, "You are connecting via: IPv6"); + + if(Main()->Config()->m_Verbose) + dbg_msg("server", "ncid=%d authed", ClientID); + Main()->OnNewClient(ClientID, ID); + } + } + else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED) + { + m_aClients[ClientID].m_LastReceived = time_get(); + if(Main()->Config()->m_Verbose) + dbg_msg("server", "ncid=%d cmd='%s'", ClientID, aBuf); + + if(str_comp(aBuf, "logout") == 0) + m_Network.Drop(ClientID, "Logout. Bye Bye ~"); + else + Main()->HandleMessage(ClientID, aBuf); + } + } + + for(int i = 0; i < NET_MAX_CLIENTS; ++i) + { + if(m_aClients[i].m_State == CClient::STATE_CONNECTED && + time_get() > m_aClients[i].m_TimeConnected + 5 * time_freq()) + { + m_Network.NetBan()->BanAddr(m_Network.ClientAddr(i), 30, "Authentication timeout."); + m_Network.Drop(i, "Authentication timeout."); + } + else if(m_aClients[i].m_State == CClient::STATE_AUTHED && + time_get() > m_aClients[i].m_LastReceived + 15 * time_freq()) + m_Network.Drop(i, "Timeout."); + } +} + +void CServer::Send(int ClientID, const char *pLine) +{ + if(!m_Ready) + return; + + if(ClientID == -1) + { + for(int i = 0; i < NET_MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State == CClient::STATE_AUTHED) + m_Network.Send(i, pLine); + } + } + else if(ClientID >= 0 && ClientID < NET_MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_AUTHED) + m_Network.Send(ClientID, pLine); +} + +void CServer::Shutdown() +{ + if(!m_Ready) + return; + + m_Network.Close(); +} diff --git a/server/src/server.h b/server/src/server.h new file mode 100644 index 0000000..f5bf162 --- /dev/null +++ b/server/src/server.h @@ -0,0 +1,46 @@ +#ifndef SERVER_H +#define SERVER_H + +#include "netban.h" +#include "network.h" + +class CServer +{ + class CClient + { + public: + enum + { + STATE_EMPTY=0, + STATE_CONNECTED, + STATE_AUTHED, + }; + + int m_State; + int64 m_TimeConnected; + int64 m_LastReceived; + }; + CClient m_aClients[NET_MAX_CLIENTS]; + + CNetwork m_Network; + CNetBan m_NetBan; + + class CMain *m_pMain; + + bool m_Ready; + + static int NewClientCallback(int ClientID, void *pUser); + static int DelClientCallback(int ClientID, const char *pReason, void *pUser); + +public: + int Init(CMain *pMain, const char *Bind, int Port); + void Update(); + void Send(int ClientID, const char *pLine); + void Shutdown(); + + CNetwork *Network() { return &m_Network; } + CNetBan *NetBan() { return &m_NetBan; } + CMain *Main() { return m_pMain; } +}; + +#endif diff --git a/server/src/system.c b/server/src/system.c new file mode 100644 index 0000000..aac93f9 --- /dev/null +++ b/server/src/system.c @@ -0,0 +1,2001 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <time.h> + +#include "system.h" + +#if defined(CONF_FAMILY_UNIX) + #include <sys/time.h> + #include <unistd.h> + + /* unix net includes */ + #include <sys/stat.h> + #include <sys/types.h> + #include <sys/socket.h> + #include <sys/ioctl.h> + #include <errno.h> + #include <netdb.h> + #include <netinet/in.h> + #include <fcntl.h> + #include <pthread.h> + #include <arpa/inet.h> + + #include <dirent.h> + + #if defined(CONF_PLATFORM_MACOSX) + #include <Carbon/Carbon.h> + #endif + +#elif defined(CONF_FAMILY_WINDOWS) + #define WIN32_LEAN_AND_MEAN + #define _WIN32_WINNT 0x0501 /* required for mingw to get getaddrinfo to work */ + #include <windows.h> + #include <winsock2.h> + #include <ws2tcpip.h> + #include <fcntl.h> + #include <direct.h> + #include <errno.h> +#else + #error NOT IMPLEMENTED +#endif + +#if defined(CONF_PLATFORM_SOLARIS) + #include <sys/filio.h> +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +IOHANDLE io_stdin() { return (IOHANDLE)stdin; } +IOHANDLE io_stdout() { return (IOHANDLE)stdout; } +IOHANDLE io_stderr() { return (IOHANDLE)stderr; } + +static DBG_LOGGER loggers[16]; +static int num_loggers = 0; + +static NETSTATS network_stats = {0}; +static MEMSTATS memory_stats = {0}; + +static NETSOCKET invalid_socket = {NETTYPE_INVALID, -1, -1}; + +void dbg_logger(DBG_LOGGER logger) +{ + loggers[num_loggers++] = logger; +} + +void dbg_assert_imp(const char *filename, int line, int test, const char *msg) +{ + if(!test) + { + dbg_msg("assert", "%s(%d): %s", filename, line, msg); + dbg_break(); + } +} + +void dbg_break() +{ + *((volatile unsigned*)0) = 0x0; +} + +void dbg_msg(const char *sys, const char *fmt, ...) +{ + va_list args; + char str[1024*8]; + char *msg; + int i, len; + + str_format(str, sizeof(str), "[%s]: ", sys); + len = strlen(str); + msg = (char *)str + len; + + va_start(args, fmt); +#if defined(CONF_FAMILY_WINDOWS) + _vsnprintf(msg, sizeof(str)-len, fmt, args); +#else + vsnprintf(msg, sizeof(str)-len, fmt, args); +#endif + va_end(args); + + for(i = 0; i < num_loggers; i++) + loggers[i](str); +} + +static void logger_stdout(const char *line) +{ + printf("%s\n", line); + fflush(stdout); +} + +static void logger_debugger(const char *line) +{ +#if defined(CONF_FAMILY_WINDOWS) + OutputDebugString(line); + OutputDebugString("\n"); +#endif +} + + +static IOHANDLE logfile = 0; +static void logger_file(const char *line) +{ + io_write(logfile, line, strlen(line)); + io_write_newline(logfile); + io_flush(logfile); +} + +void dbg_logger_stdout() { dbg_logger(logger_stdout); } +void dbg_logger_debugger() { dbg_logger(logger_debugger); } +void dbg_logger_file(const char *filename) +{ + logfile = io_open(filename, IOFLAG_WRITE); + if(logfile) + dbg_logger(logger_file); + else + dbg_msg("dbg/logger", "failed to open '%s' for logging", filename); + +} +/* */ + +typedef struct MEMHEADER +{ + const char *filename; + int line; + int size; + struct MEMHEADER *prev; + struct MEMHEADER *next; +} MEMHEADER; + +typedef struct MEMTAIL +{ + int guard; +} MEMTAIL; + +static struct MEMHEADER *first = 0; +static const int MEM_GUARD_VAL = 0xbaadc0de; + +void *mem_alloc_debug(const char *filename, int line, unsigned size, unsigned alignment) +{ + /* TODO: fix alignment */ + /* TODO: add debugging */ + MEMTAIL *tail; + MEMHEADER *header = (struct MEMHEADER *)malloc(size+sizeof(MEMHEADER)+sizeof(MEMTAIL)); + dbg_assert(header != 0, "mem_alloc failure"); + if(!header) + return NULL; + tail = (struct MEMTAIL *)(((char*)(header+1))+size); + header->size = size; + header->filename = filename; + header->line = line; + + memory_stats.allocated += header->size; + memory_stats.total_allocations++; + memory_stats.active_allocations++; + + tail->guard = MEM_GUARD_VAL; + + header->prev = (MEMHEADER *)0; + header->next = first; + if(first) + first->prev = header; + first = header; + + /*dbg_msg("mem", "++ %p", header+1); */ + return header+1; +} + +void mem_free(void *p) +{ + if(p) + { + MEMHEADER *header = (MEMHEADER *)p - 1; + MEMTAIL *tail = (MEMTAIL *)(((char*)(header+1))+header->size); + + if(tail->guard != MEM_GUARD_VAL) + dbg_msg("mem", "!! %p", p); + /* dbg_msg("mem", "-- %p", p); */ + memory_stats.allocated -= header->size; + memory_stats.active_allocations--; + + if(header->prev) + header->prev->next = header->next; + else + first = header->next; + if(header->next) + header->next->prev = header->prev; + + free(header); + } +} + +void mem_debug_dump(IOHANDLE file) +{ + char buf[1024]; + MEMHEADER *header = first; + if(!file) + file = io_open("memory.txt", IOFLAG_WRITE); + + if(file) + { + while(header) + { + str_format(buf, sizeof(buf), "%s(%d): %d", header->filename, header->line, header->size); + io_write(file, buf, strlen(buf)); + io_write_newline(file); + header = header->next; + } + + io_close(file); + } +} + + +void mem_copy(void *dest, const void *source, unsigned size) +{ + memcpy(dest, source, size); +} + +void mem_move(void *dest, const void *source, unsigned size) +{ + memmove(dest, source, size); +} + +void mem_zero(void *block, unsigned size) +{ + memset(block, 0, size); +} + +int mem_check_imp() +{ + MEMHEADER *header = first; + while(header) + { + MEMTAIL *tail = (MEMTAIL *)(((char*)(header+1))+header->size); + if(tail->guard != MEM_GUARD_VAL) + { + dbg_msg("mem", "Memory check failed at %s(%d): %d", header->filename, header->line, header->size); + return 0; + } + header = header->next; + } + + return 1; +} + +IOHANDLE io_open(const char *filename, int flags) +{ + if(flags == IOFLAG_READ) + { + #if defined(CONF_FAMILY_WINDOWS) + // check for filename case sensitive + WIN32_FIND_DATA finddata; + HANDLE handle; + int length; + + length = str_length(filename); + if(!filename || !length || filename[length-1] == '\\') + return 0x0; + handle = FindFirstFile(filename, &finddata); + if(handle == INVALID_HANDLE_VALUE) + return 0x0; + else if(str_comp(filename+length-str_length(finddata.cFileName), finddata.cFileName) != 0) + { + FindClose(handle); + return 0x0; + } + FindClose(handle); + #endif + return (IOHANDLE)fopen(filename, "rb"); + } + if(flags == IOFLAG_WRITE) + return (IOHANDLE)fopen(filename, "wb"); + return 0x0; +} + +unsigned io_read(IOHANDLE io, void *buffer, unsigned size) +{ + return fread(buffer, 1, size, (FILE*)io); +} + +unsigned io_skip(IOHANDLE io, int size) +{ + fseek((FILE*)io, size, SEEK_CUR); + return size; +} + +int io_seek(IOHANDLE io, int offset, int origin) +{ + int real_origin; + + switch(origin) + { + case IOSEEK_START: + real_origin = SEEK_SET; + break; + case IOSEEK_CUR: + real_origin = SEEK_CUR; + break; + case IOSEEK_END: + real_origin = SEEK_END; + break; + default: + return -1; + } + + return fseek((FILE*)io, offset, real_origin); +} + +long int io_tell(IOHANDLE io) +{ + return ftell((FILE*)io); +} + +long int io_length(IOHANDLE io) +{ + long int length; + io_seek(io, 0, IOSEEK_END); + length = io_tell(io); + io_seek(io, 0, IOSEEK_START); + return length; +} + +unsigned io_write(IOHANDLE io, const void *buffer, unsigned size) +{ + return fwrite(buffer, 1, size, (FILE*)io); +} + +unsigned io_write_newline(IOHANDLE io) +{ +#if defined(CONF_FAMILY_WINDOWS) + return fwrite("\r\n", 1, 2, (FILE*)io); +#else + return fwrite("\n", 1, 1, (FILE*)io); +#endif +} + +int io_close(IOHANDLE io) +{ + fclose((FILE*)io); + return 1; +} + +int io_flush(IOHANDLE io) +{ + fflush((FILE*)io); + return 0; +} + +void *thread_create(void (*threadfunc)(void *), void *u) +{ +#if defined(CONF_FAMILY_UNIX) + pthread_t id; + pthread_create(&id, NULL, (void *(*)(void*))threadfunc, u); + return (void*)id; +#elif defined(CONF_FAMILY_WINDOWS) + return CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)threadfunc, u, 0, NULL); +#else + #error not implemented +#endif +} + +void thread_wait(void *thread) +{ +#if defined(CONF_FAMILY_UNIX) + pthread_join((pthread_t)thread, NULL); +#elif defined(CONF_FAMILY_WINDOWS) + WaitForSingleObject((HANDLE)thread, INFINITE); +#else + #error not implemented +#endif +} + +void thread_destroy(void *thread) +{ +#if defined(CONF_FAMILY_UNIX) + void *r = 0; + pthread_join((pthread_t)thread, &r); +#else + /*#error not implemented*/ +#endif +} + +void thread_yield() +{ +#if defined(CONF_FAMILY_UNIX) + sched_yield(); +#elif defined(CONF_FAMILY_WINDOWS) + Sleep(0); +#else + #error not implemented +#endif +} + +void thread_sleep(int milliseconds) +{ +#if defined(CONF_FAMILY_UNIX) + usleep(milliseconds*1000); +#elif defined(CONF_FAMILY_WINDOWS) + Sleep(milliseconds); +#else + #error not implemented +#endif +} + +void thread_detach(void *thread) +{ +#if defined(CONF_FAMILY_UNIX) + pthread_detach((pthread_t)(thread)); +#elif defined(CONF_FAMILY_WINDOWS) + CloseHandle(thread); +#else + #error not implemented +#endif +} + + + + +#if defined(CONF_FAMILY_UNIX) +typedef pthread_mutex_t LOCKINTERNAL; +#elif defined(CONF_FAMILY_WINDOWS) +typedef CRITICAL_SECTION LOCKINTERNAL; +#else + #error not implemented on this platform +#endif + +LOCK lock_create() +{ + LOCKINTERNAL *lock = (LOCKINTERNAL*)mem_alloc(sizeof(LOCKINTERNAL), 4); + +#if defined(CONF_FAMILY_UNIX) + pthread_mutex_init(lock, 0x0); +#elif defined(CONF_FAMILY_WINDOWS) + InitializeCriticalSection((LPCRITICAL_SECTION)lock); +#else + #error not implemented on this platform +#endif + return (LOCK)lock; +} + +void lock_destroy(LOCK lock) +{ +#if defined(CONF_FAMILY_UNIX) + pthread_mutex_destroy((LOCKINTERNAL *)lock); +#elif defined(CONF_FAMILY_WINDOWS) + DeleteCriticalSection((LPCRITICAL_SECTION)lock); +#else + #error not implemented on this platform +#endif + mem_free(lock); +} + +int lock_try(LOCK lock) +{ +#if defined(CONF_FAMILY_UNIX) + return pthread_mutex_trylock((LOCKINTERNAL *)lock); +#elif defined(CONF_FAMILY_WINDOWS) + return !TryEnterCriticalSection((LPCRITICAL_SECTION)lock); +#else + #error not implemented on this platform +#endif +} + +void lock_wait(LOCK lock) +{ +#if defined(CONF_FAMILY_UNIX) + pthread_mutex_lock((LOCKINTERNAL *)lock); +#elif defined(CONF_FAMILY_WINDOWS) + EnterCriticalSection((LPCRITICAL_SECTION)lock); +#else + #error not implemented on this platform +#endif +} + +void lock_release(LOCK lock) +{ +#if defined(CONF_FAMILY_UNIX) + pthread_mutex_unlock((LOCKINTERNAL *)lock); +#elif defined(CONF_FAMILY_WINDOWS) + LeaveCriticalSection((LPCRITICAL_SECTION)lock); +#else + #error not implemented on this platform +#endif +} + +#if !defined(CONF_PLATFORM_MACOSX) + #if defined(CONF_FAMILY_UNIX) + void semaphore_init(SEMAPHORE *sem) { sem_init(sem, 0, 0); } + void semaphore_wait(SEMAPHORE *sem) { sem_wait(sem); } + void semaphore_signal(SEMAPHORE *sem) { sem_post(sem); } + void semaphore_destroy(SEMAPHORE *sem) { sem_destroy(sem); } + #elif defined(CONF_FAMILY_WINDOWS) + void semaphore_init(SEMAPHORE *sem) { *sem = CreateSemaphore(0, 0, 10000, 0); } + void semaphore_wait(SEMAPHORE *sem) { WaitForSingleObject((HANDLE)*sem, INFINITE); } + void semaphore_signal(SEMAPHORE *sem) { ReleaseSemaphore((HANDLE)*sem, 1, NULL); } + void semaphore_destroy(SEMAPHORE *sem) { CloseHandle((HANDLE)*sem); } + #else + #error not implemented on this platform + #endif +#endif + + +/* ----- time ----- */ +int64 time_get() +{ +#if defined(CONF_FAMILY_UNIX) + struct timeval val; + gettimeofday(&val, NULL); + return (int64)val.tv_sec*(int64)1000000+(int64)val.tv_usec; +#elif defined(CONF_FAMILY_WINDOWS) + static int64 last = 0; + int64 t; + QueryPerformanceCounter((PLARGE_INTEGER)&t); + if(t<last) /* for some reason, QPC can return values in the past */ + return last; + last = t; + return t; +#else + #error not implemented +#endif +} + +int64 time_freq() +{ +#if defined(CONF_FAMILY_UNIX) + return 1000000; +#elif defined(CONF_FAMILY_WINDOWS) + int64 t; + QueryPerformanceFrequency((PLARGE_INTEGER)&t); + return t; +#else + #error not implemented +#endif +} + +/* ----- network ----- */ +static void netaddr_to_sockaddr_in(const NETADDR *src, struct sockaddr_in *dest) +{ + mem_zero(dest, sizeof(struct sockaddr_in)); + if(src->type != NETTYPE_IPV4) + { + dbg_msg("system", "couldn't convert NETADDR of type %d to ipv4", src->type); + return; + } + + dest->sin_family = AF_INET; + dest->sin_port = htons(src->port); + mem_copy(&dest->sin_addr.s_addr, src->ip, 4); +} + +static void netaddr_to_sockaddr_in6(const NETADDR *src, struct sockaddr_in6 *dest) +{ + mem_zero(dest, sizeof(struct sockaddr_in6)); + if(src->type != NETTYPE_IPV6) + { + dbg_msg("system", "couldn't not convert NETADDR of type %d to ipv6", src->type); + return; + } + + dest->sin6_family = AF_INET6; + dest->sin6_port = htons(src->port); + mem_copy(&dest->sin6_addr.s6_addr, src->ip, 16); +} + +static void sockaddr_to_netaddr(const struct sockaddr *src, NETADDR *dst) +{ + if(src->sa_family == AF_INET) + { + mem_zero(dst, sizeof(NETADDR)); + dst->type = NETTYPE_IPV4; + dst->port = htons(((struct sockaddr_in*)src)->sin_port); + mem_copy(dst->ip, &((struct sockaddr_in*)src)->sin_addr.s_addr, 4); + } + else if(src->sa_family == AF_INET6) + { + mem_zero(dst, sizeof(NETADDR)); + dst->type = NETTYPE_IPV6; + dst->port = htons(((struct sockaddr_in6*)src)->sin6_port); + mem_copy(dst->ip, &((struct sockaddr_in6*)src)->sin6_addr.s6_addr, 16); + } + else + { + mem_zero(dst, sizeof(struct sockaddr)); + dbg_msg("system", "couldn't convert sockaddr of family %d", src->sa_family); + } +} + +int net_addr_comp(const NETADDR *a, const NETADDR *b) +{ + return mem_comp(a, b, sizeof(NETADDR)); +} + +void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port) +{ + if(addr->type == NETTYPE_IPV4) + { + if(add_port != 0) + str_format(string, max_length, "%d.%d.%d.%d:%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3], addr->port); + else + str_format(string, max_length, "%d.%d.%d.%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]); + } + else if(addr->type == NETTYPE_IPV6) + { + if(add_port != 0) + str_format(string, max_length, "[%x:%x:%x:%x:%x:%x:%x:%x]:%d", + (addr->ip[0]<<8)|addr->ip[1], (addr->ip[2]<<8)|addr->ip[3], (addr->ip[4]<<8)|addr->ip[5], (addr->ip[6]<<8)|addr->ip[7], + (addr->ip[8]<<8)|addr->ip[9], (addr->ip[10]<<8)|addr->ip[11], (addr->ip[12]<<8)|addr->ip[13], (addr->ip[14]<<8)|addr->ip[15], + addr->port); + else + str_format(string, max_length, "[%x:%x:%x:%x:%x:%x:%x:%x]", + (addr->ip[0]<<8)|addr->ip[1], (addr->ip[2]<<8)|addr->ip[3], (addr->ip[4]<<8)|addr->ip[5], (addr->ip[6]<<8)|addr->ip[7], + (addr->ip[8]<<8)|addr->ip[9], (addr->ip[10]<<8)|addr->ip[11], (addr->ip[12]<<8)|addr->ip[13], (addr->ip[14]<<8)|addr->ip[15]); + } + else + str_format(string, max_length, "unknown type %d", addr->type); +} + +static int priv_net_extract(const char *hostname, char *host, int max_host, int *port) +{ + int i; + + *port = 0; + host[0] = 0; + + if(hostname[0] == '[') + { + // ipv6 mode + for(i = 1; i < max_host && hostname[i] && hostname[i] != ']'; i++) + host[i-1] = hostname[i]; + host[i-1] = 0; + if(hostname[i] != ']') // malformatted + return -1; + + i++; + if(hostname[i] == ':') + *port = atol(hostname+i+1); + } + else + { + // generic mode (ipv4, hostname etc) + for(i = 0; i < max_host-1 && hostname[i] && hostname[i] != ':'; i++) + host[i] = hostname[i]; + host[i] = 0; + + if(hostname[i] == ':') + *port = atol(hostname+i+1); + } + + return 0; +} + +int net_host_lookup(const char *hostname, NETADDR *addr, int types) +{ + struct addrinfo hints; + struct addrinfo *result; + int e; + char host[256]; + int port = 0; + + if(priv_net_extract(hostname, host, sizeof(host), &port)) + return -1; + /* + dbg_msg("host lookup", "host='%s' port=%d %d", host, port, types); + */ + + mem_zero(&hints, sizeof(hints)); + + hints.ai_family = AF_UNSPEC; + + if(types == NETTYPE_IPV4) + hints.ai_family = AF_INET; + else if(types == NETTYPE_IPV6) + hints.ai_family = AF_INET6; + + e = getaddrinfo(host, NULL, &hints, &result); + if(e != 0 || !result) + return -1; + + sockaddr_to_netaddr(result->ai_addr, addr); + freeaddrinfo(result); + addr->port = port; + return 0; +} + +static int parse_int(int *out, const char **str) +{ + int i = 0; + *out = 0; + if(**str < '0' || **str > '9') + return -1; + + i = **str - '0'; + (*str)++; + + while(1) + { + if(**str < '0' || **str > '9') + { + *out = i; + return 0; + } + + i = (i*10) + (**str - '0'); + (*str)++; + } + + return 0; +} + +static int parse_char(char c, const char **str) +{ + if(**str != c) return -1; + (*str)++; + return 0; +} + +static int parse_uint8(unsigned char *out, const char **str) +{ + int i; + if(parse_int(&i, str) != 0) return -1; + if(i < 0 || i > 0xff) return -1; + *out = i; + return 0; +} + +static int parse_uint16(unsigned short *out, const char **str) +{ + int i; + if(parse_int(&i, str) != 0) return -1; + if(i < 0 || i > 0xffff) return -1; + *out = i; + return 0; +} + +int net_addr_from_str(NETADDR *addr, const char *string) +{ + const char *str = string; + mem_zero(addr, sizeof(NETADDR)); + + if(str[0] == '[') + { + /* ipv6 */ + struct sockaddr_in6 sa6; + char buf[128]; + int i; + str++; + for(i = 0; i < 127 && str[i] && str[i] != ']'; i++) + buf[i] = str[i]; + buf[i] = 0; + str += i; +#if defined(CONF_FAMILY_WINDOWS) + { + int size; + sa6.sin6_family = AF_INET6; + size = (int)sizeof(sa6); + if(WSAStringToAddress(buf, AF_INET6, NULL, (struct sockaddr *)&sa6, &size) != 0) + return -1; + } +#else + if(inet_pton(AF_INET6, buf, &sa6) != 1) + return -1; +#endif + sockaddr_to_netaddr((struct sockaddr *)&sa6, addr); + + if(*str == ']') + { + str++; + if(*str == ':') + { + str++; + if(parse_uint16(&addr->port, &str)) + return -1; + } + } + else + return -1; + + return 0; + } + else + { + /* ipv4 */ + if(parse_uint8(&addr->ip[0], &str)) return -1; + if(parse_char('.', &str)) return -1; + if(parse_uint8(&addr->ip[1], &str)) return -1; + if(parse_char('.', &str)) return -1; + if(parse_uint8(&addr->ip[2], &str)) return -1; + if(parse_char('.', &str)) return -1; + if(parse_uint8(&addr->ip[3], &str)) return -1; + if(*str == ':') + { + str++; + if(parse_uint16(&addr->port, &str)) return -1; + } + + addr->type = NETTYPE_IPV4; + } + + return 0; +} + +static void priv_net_close_socket(int sock) +{ +#if defined(CONF_FAMILY_WINDOWS) + closesocket(sock); +#else + close(sock); +#endif +} + +static int priv_net_close_all_sockets(NETSOCKET sock) +{ + /* close down ipv4 */ + if(sock.ipv4sock >= 0) + { + priv_net_close_socket(sock.ipv4sock); + sock.ipv4sock = -1; + sock.type &= ~NETTYPE_IPV4; + } + + /* close down ipv6 */ + if(sock.ipv6sock >= 0) + { + priv_net_close_socket(sock.ipv6sock); + sock.ipv6sock = -1; + sock.type &= ~NETTYPE_IPV6; + } + return 0; +} + +static int priv_net_create_socket(int domain, int type, struct sockaddr *addr, int sockaddrlen) +{ + int sock, e; + + /* create socket */ + sock = socket(domain, type, 0); + if(sock < 0) + { +#if defined(CONF_FAMILY_WINDOWS) + char buf[128]; + int error = WSAGetLastError(); + if(FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, buf, sizeof(buf), 0) == 0) + buf[0] = 0; + dbg_msg("net", "failed to create socket with domain %d and type %d (%d '%s')", domain, type, error, buf); +#else + dbg_msg("net", "failed to create socket with domain %d and type %d (%d '%s')", domain, type, errno, strerror(errno)); +#endif + return -1; + } + + /* set to IPv6 only if thats what we are creating */ +#if defined(IPV6_V6ONLY) /* windows sdk 6.1 and higher */ + if(domain == AF_INET6) + { + int ipv6only = 1; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&ipv6only, sizeof(ipv6only)); + } +#endif + + if(type == SOCK_STREAM) + { + int tmp = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp)); + } + + /* bind the socket */ + e = bind(sock, addr, sockaddrlen); + if(e != 0) + { +#if defined(CONF_FAMILY_WINDOWS) + char buf[128]; + int error = WSAGetLastError(); + if(FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, buf, sizeof(buf), 0) == 0) + buf[0] = 0; + dbg_msg("net", "failed to bind socket with domain %d and type %d (%d '%s')", domain, type, error, buf); +#else + dbg_msg("net", "failed to bind socket with domain %d and type %d (%d '%s')", domain, type, errno, strerror(errno)); +#endif + priv_net_close_socket(sock); + return -1; + } + + /* return the newly created socket */ + return sock; +} + +NETSOCKET net_udp_create(NETADDR bindaddr) +{ + NETSOCKET sock = invalid_socket; + NETADDR tmpbindaddr = bindaddr; + int broadcast = 1; + int recvsize = 65536; + + if(bindaddr.type&NETTYPE_IPV4) + { + struct sockaddr_in addr; + int socket = -1; + + /* bind, we should check for error */ + tmpbindaddr.type = NETTYPE_IPV4; + netaddr_to_sockaddr_in(&tmpbindaddr, &addr); + socket = priv_net_create_socket(AF_INET, SOCK_DGRAM, (struct sockaddr *)&addr, sizeof(addr)); + if(socket >= 0) + { + sock.type |= NETTYPE_IPV4; + sock.ipv4sock = socket; + + /* set broadcast */ + setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof(broadcast)); + + /* set receive buffer size */ + setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&recvsize, sizeof(recvsize)); + } + } + + if(bindaddr.type&NETTYPE_IPV6) + { + struct sockaddr_in6 addr; + int socket = -1; + + /* bind, we should check for error */ + tmpbindaddr.type = NETTYPE_IPV6; + netaddr_to_sockaddr_in6(&tmpbindaddr, &addr); + socket = priv_net_create_socket(AF_INET6, SOCK_DGRAM, (struct sockaddr *)&addr, sizeof(addr)); + if(socket >= 0) + { + sock.type |= NETTYPE_IPV6; + sock.ipv6sock = socket; + + /* set broadcast */ + setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof(broadcast)); + + /* set receive buffer size */ + setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&recvsize, sizeof(recvsize)); + } + } + + /* set non-blocking */ + net_set_non_blocking(sock); + + /* return */ + return sock; +} + +int net_udp_send(NETSOCKET sock, const NETADDR *addr, const void *data, int size) +{ + int d = -1; + + if(addr->type&NETTYPE_IPV4) + { + if(sock.ipv4sock >= 0) + { + struct sockaddr_in sa; + if(addr->type&NETTYPE_LINK_BROADCAST) + { + mem_zero(&sa, sizeof(sa)); + sa.sin_port = htons(addr->port); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = INADDR_BROADCAST; + } + else + netaddr_to_sockaddr_in(addr, &sa); + + d = sendto((int)sock.ipv4sock, (const char*)data, size, 0, (struct sockaddr *)&sa, sizeof(sa)); + } + else + dbg_msg("net", "can't sent ipv4 traffic to this socket"); + } + + if(addr->type&NETTYPE_IPV6) + { + if(sock.ipv6sock >= 0) + { + struct sockaddr_in6 sa; + if(addr->type&NETTYPE_LINK_BROADCAST) + { + mem_zero(&sa, sizeof(sa)); + sa.sin6_port = htons(addr->port); + sa.sin6_family = AF_INET6; + sa.sin6_addr.s6_addr[0] = 0xff; /* multicast */ + sa.sin6_addr.s6_addr[1] = 0x02; /* link local scope */ + sa.sin6_addr.s6_addr[15] = 1; /* all nodes */ + } + else + netaddr_to_sockaddr_in6(addr, &sa); + + d = sendto((int)sock.ipv6sock, (const char*)data, size, 0, (struct sockaddr *)&sa, sizeof(sa)); + } + else + dbg_msg("net", "can't sent ipv6 traffic to this socket"); + } + /* + else + dbg_msg("net", "can't sent to network of type %d", addr->type); + */ + + /*if(d < 0) + { + char addrstr[256]; + net_addr_str(addr, addrstr, sizeof(addrstr)); + + dbg_msg("net", "sendto error (%d '%s')", errno, strerror(errno)); + dbg_msg("net", "\tsock = %d %x", sock, sock); + dbg_msg("net", "\tsize = %d %x", size, size); + dbg_msg("net", "\taddr = %s", addrstr); + + }*/ + network_stats.sent_bytes += size; + network_stats.sent_packets++; + return d; +} + +int net_udp_recv(NETSOCKET sock, NETADDR *addr, void *data, int maxsize) +{ + char sockaddrbuf[128]; + socklen_t fromlen;// = sizeof(sockaddrbuf); + int bytes = 0; + + if(bytes == 0 && sock.ipv4sock >= 0) + { + fromlen = sizeof(struct sockaddr_in); + bytes = recvfrom(sock.ipv4sock, (char*)data, maxsize, 0, (struct sockaddr *)&sockaddrbuf, &fromlen); + } + + if(bytes <= 0 && sock.ipv6sock >= 0) + { + fromlen = sizeof(struct sockaddr_in6); + bytes = recvfrom(sock.ipv6sock, (char*)data, maxsize, 0, (struct sockaddr *)&sockaddrbuf, &fromlen); + } + + if(bytes > 0) + { + sockaddr_to_netaddr((struct sockaddr *)&sockaddrbuf, addr); + network_stats.recv_bytes += bytes; + network_stats.recv_packets++; + return bytes; + } + else if(bytes == 0) + return 0; + return -1; /* error */ +} + +int net_udp_close(NETSOCKET sock) +{ + return priv_net_close_all_sockets(sock); +} + +NETSOCKET net_tcp_create(NETADDR bindaddr) +{ + NETSOCKET sock = invalid_socket; + NETADDR tmpbindaddr = bindaddr; + + if(bindaddr.type&NETTYPE_IPV4) + { + struct sockaddr_in addr; + int socket = -1; + + /* bind, we should check for error */ + tmpbindaddr.type = NETTYPE_IPV4; + netaddr_to_sockaddr_in(&tmpbindaddr, &addr); + socket = priv_net_create_socket(AF_INET, SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr)); + if(socket >= 0) + { + sock.type |= NETTYPE_IPV4; + sock.ipv4sock = socket; + } + } + + if(bindaddr.type&NETTYPE_IPV6) + { + struct sockaddr_in6 addr; + int socket = -1; + + /* bind, we should check for error */ + tmpbindaddr.type = NETTYPE_IPV6; + netaddr_to_sockaddr_in6(&tmpbindaddr, &addr); + socket = priv_net_create_socket(AF_INET6, SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr)); + if(socket >= 0) + { + sock.type |= NETTYPE_IPV6; + sock.ipv6sock = socket; + } + } + + /* return */ + return sock; +} + +int net_set_non_blocking(NETSOCKET sock) +{ + unsigned long mode = 1; + if(sock.ipv4sock >= 0) + { +#if defined(CONF_FAMILY_WINDOWS) + ioctlsocket(sock.ipv4sock, FIONBIO, (unsigned long *)&mode); +#else + ioctl(sock.ipv4sock, FIONBIO, (unsigned long *)&mode); +#endif + } + + if(sock.ipv6sock >= 0) + { +#if defined(CONF_FAMILY_WINDOWS) + ioctlsocket(sock.ipv6sock, FIONBIO, (unsigned long *)&mode); +#else + ioctl(sock.ipv6sock, FIONBIO, (unsigned long *)&mode); +#endif + } + + return 0; +} + +int net_set_blocking(NETSOCKET sock) +{ + unsigned long mode = 0; + if(sock.ipv4sock >= 0) + { +#if defined(CONF_FAMILY_WINDOWS) + ioctlsocket(sock.ipv4sock, FIONBIO, (unsigned long *)&mode); +#else + ioctl(sock.ipv4sock, FIONBIO, (unsigned long *)&mode); +#endif + } + + if(sock.ipv6sock >= 0) + { +#if defined(CONF_FAMILY_WINDOWS) + ioctlsocket(sock.ipv6sock, FIONBIO, (unsigned long *)&mode); +#else + ioctl(sock.ipv6sock, FIONBIO, (unsigned long *)&mode); +#endif + } + + return 0; +} + +int net_tcp_listen(NETSOCKET sock, int backlog) +{ + int err = -1; + if(sock.ipv4sock >= 0) + err = listen(sock.ipv4sock, backlog); + if(sock.ipv6sock >= 0) + err = listen(sock.ipv6sock, backlog); + return err; +} + +int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a) +{ + int s; + socklen_t sockaddr_len; + + *new_sock = invalid_socket; + + if(sock.ipv4sock >= 0) + { + struct sockaddr_in addr; + sockaddr_len = sizeof(addr); + + s = accept(sock.ipv4sock, (struct sockaddr *)&addr, &sockaddr_len); + + if (s != -1) + { + sockaddr_to_netaddr((const struct sockaddr *)&addr, a); + new_sock->type = NETTYPE_IPV4; + new_sock->ipv4sock = s; + return s; + } + } + + if(sock.ipv6sock >= 0) + { + struct sockaddr_in6 addr; + sockaddr_len = sizeof(addr); + + s = accept(sock.ipv6sock, (struct sockaddr *)&addr, &sockaddr_len); + + if (s != -1) + { + sockaddr_to_netaddr((const struct sockaddr *)&addr, a); + new_sock->type = NETTYPE_IPV6; + new_sock->ipv6sock = s; + return s; + } + } + + return -1; +} + +int net_tcp_connect(NETSOCKET sock, const NETADDR *a) +{ + if(a->type&NETTYPE_IPV4) + { + struct sockaddr_in addr; + netaddr_to_sockaddr_in(a, &addr); + return connect(sock.ipv4sock, (struct sockaddr *)&addr, sizeof(addr)); + } + + if(a->type&NETTYPE_IPV6) + { + struct sockaddr_in6 addr; + netaddr_to_sockaddr_in6(a, &addr); + return connect(sock.ipv6sock, (struct sockaddr *)&addr, sizeof(addr)); + } + + return -1; +} + +int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr) +{ + int res = 0; + + net_set_non_blocking(sock); + res = net_tcp_connect(sock, &bindaddr); + net_set_blocking(sock); + + return res; +} + +int net_tcp_send(NETSOCKET sock, const void *data, int size) +{ + int bytes = -1; + + if(sock.ipv4sock >= 0) + bytes = send((int)sock.ipv4sock, (const char*)data, size, 0); + if(sock.ipv6sock >= 0) + bytes = send((int)sock.ipv6sock, (const char*)data, size, 0); + + return bytes; +} + +int net_tcp_recv(NETSOCKET sock, void *data, int maxsize) +{ + int bytes = -1; + + if(sock.ipv4sock >= 0) + bytes = recv((int)sock.ipv4sock, (char*)data, maxsize, 0); + if(sock.ipv6sock >= 0) + bytes = recv((int)sock.ipv6sock, (char*)data, maxsize, 0); + + return bytes; +} + +int net_tcp_close(NETSOCKET sock) +{ + return priv_net_close_all_sockets(sock); +} + +int net_errno() +{ +#if defined(CONF_FAMILY_WINDOWS) + return WSAGetLastError(); +#else + return errno; +#endif +} + +int net_would_block() +{ +#if defined(CONF_FAMILY_WINDOWS) + return net_errno() == WSAEWOULDBLOCK; +#else + return net_errno() == EWOULDBLOCK; +#endif +} + +int net_init() +{ +#if defined(CONF_FAMILY_WINDOWS) + WSADATA wsaData; + int err = WSAStartup(MAKEWORD(1, 1), &wsaData); + dbg_assert(err == 0, "network initialization failed."); + return err==0?0:1; +#endif + + return 0; +} + +int fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user) +{ +#if defined(CONF_FAMILY_WINDOWS) + WIN32_FIND_DATA finddata; + HANDLE handle; + char buffer[1024*2]; + int length; + str_format(buffer, sizeof(buffer), "%s/*", dir); + + handle = FindFirstFileA(buffer, &finddata); + + if (handle == INVALID_HANDLE_VALUE) + return 0; + + str_format(buffer, sizeof(buffer), "%s/", dir); + length = str_length(buffer); + + /* add all the entries */ + do + { + str_copy(buffer+length, finddata.cFileName, (int)sizeof(buffer)-length); + if(cb(finddata.cFileName, fs_is_dir(buffer), type, user)) + break; + } + while (FindNextFileA(handle, &finddata)); + + FindClose(handle); + return 0; +#else + struct dirent *entry; + char buffer[1024*2]; + int length; + DIR *d = opendir(dir); + + if(!d) + return 0; + + str_format(buffer, sizeof(buffer), "%s/", dir); + length = str_length(buffer); + + while((entry = readdir(d)) != NULL) + { + str_copy(buffer+length, entry->d_name, (int)sizeof(buffer)-length); + if(cb(entry->d_name, fs_is_dir(buffer), type, user)) + break; + } + + /* close the directory and return */ + closedir(d); + return 0; +#endif +} + +int fs_storage_path(const char *appname, char *path, int max) +{ +#if defined(CONF_FAMILY_WINDOWS) + char *home = getenv("APPDATA"); + if(!home) + return -1; + _snprintf(path, max, "%s/%s", home, appname); + return 0; +#else + char *home = getenv("HOME"); +#if !defined(CONF_PLATFORM_MACOSX) + int i; +#endif + if(!home) + return -1; + +#if defined(CONF_PLATFORM_MACOSX) + snprintf(path, max, "%s/Library/Application Support/%s", home, appname); +#else + snprintf(path, max, "%s/.%s", home, appname); + for(i = strlen(home)+2; path[i]; i++) + path[i] = tolower(path[i]); +#endif + + return 0; +#endif +} + +int fs_makedir(const char *path) +{ +#if defined(CONF_FAMILY_WINDOWS) + if(_mkdir(path) == 0) + return 0; + if(errno == EEXIST) + return 0; + return -1; +#else + if(mkdir(path, 0755) == 0) + return 0; + if(errno == EEXIST) + return 0; + return -1; +#endif +} + +int fs_is_dir(const char *path) +{ +#if defined(CONF_FAMILY_WINDOWS) + /* TODO: do this smarter */ + WIN32_FIND_DATA finddata; + HANDLE handle; + char buffer[1024*2]; + str_format(buffer, sizeof(buffer), "%s/*", path); + + if ((handle = FindFirstFileA(buffer, &finddata)) == INVALID_HANDLE_VALUE) + return 0; + + FindClose(handle); + return 1; +#else + struct stat sb; + if (stat(path, &sb) == -1) + return 0; + + if (S_ISDIR(sb.st_mode)) + return 1; + else + return 0; +#endif +} + +int fs_chdir(const char *path) +{ + if(fs_is_dir(path)) + { + if(chdir(path)) + return 1; + else + return 0; + } + else + return 1; +} + +char *fs_getcwd(char *buffer, int buffer_size) +{ + if(buffer == 0) + return 0; +#if defined(CONF_FAMILY_WINDOWS) + return _getcwd(buffer, buffer_size); +#else + return getcwd(buffer, buffer_size); +#endif +} + +int fs_parent_dir(char *path) +{ + char *parent = 0; + for(; *path; ++path) + { + if(*path == '/' || *path == '\\') + parent = path; + } + + if(parent) + { + *parent = 0; + return 0; + } + return 1; +} + +int fs_remove(const char *filename) +{ + if(remove(filename) != 0) + return 1; + return 0; +} + +int fs_rename(const char *oldname, const char *newname) +{ + if(rename(oldname, newname) != 0) + return 1; + return 0; +} + +void swap_endian(void *data, unsigned elem_size, unsigned num) +{ + char *src = (char*) data; + char *dst = src + (elem_size - 1); + + while(num) + { + unsigned n = elem_size>>1; + char tmp; + while(n) + { + tmp = *src; + *src = *dst; + *dst = tmp; + + src++; + dst--; + n--; + } + + src = src + (elem_size>>1); + dst = src + (elem_size - 1); + num--; + } +} + +int net_socket_read_wait(NETSOCKET sock, int time) +{ + struct timeval tv; + fd_set readfds; + int sockid; + + tv.tv_sec = 0; + tv.tv_usec = 1000*time; + sockid = 0; + + FD_ZERO(&readfds); + if(sock.ipv4sock >= 0) + { + FD_SET(sock.ipv4sock, &readfds); + sockid = sock.ipv4sock; + } + if(sock.ipv6sock >= 0) + { + FD_SET(sock.ipv6sock, &readfds); + if(sock.ipv6sock > sockid) + sockid = sock.ipv6sock; + } + + /* don't care about writefds and exceptfds */ + select(sockid+1, &readfds, NULL, NULL, &tv); + + if(sock.ipv4sock >= 0 && FD_ISSET(sock.ipv4sock, &readfds)) + return 1; + + if(sock.ipv6sock >= 0 && FD_ISSET(sock.ipv6sock, &readfds)) + return 1; + + return 0; +} + +int time_timestamp() +{ + return time(0); +} + +void str_append(char *dst, const char *src, int dst_size) +{ + int s = strlen(dst); + int i = 0; + while(s < dst_size) + { + dst[s] = src[i]; + if(!src[i]) /* check for null termination */ + break; + s++; + i++; + } + + dst[dst_size-1] = 0; /* assure null termination */ +} + +void str_copy(char *dst, const char *src, int dst_size) +{ + strncpy(dst, src, dst_size); + dst[dst_size-1] = 0; /* assure null termination */ +} + +int str_length(const char *str) +{ + return (int)strlen(str); +} + +void str_format(char *buffer, int buffer_size, const char *format, ...) +{ +#if defined(CONF_FAMILY_WINDOWS) + va_list ap; + va_start(ap, format); + _vsnprintf(buffer, buffer_size, format, ap); + va_end(ap); +#else + va_list ap; + va_start(ap, format); + vsnprintf(buffer, buffer_size, format, ap); + va_end(ap); +#endif + + buffer[buffer_size-1] = 0; /* assure null termination */ +} + + + +/* makes sure that the string only contains the characters between 32 and 127 */ +void str_sanitize_strong(char *str_in) +{ + unsigned char *str = (unsigned char *)str_in; + while(*str) + { + *str &= 0x7f; + if(*str < 32) + *str = 32; + str++; + } +} + +/* makes sure that the string only contains the characters between 32 and 255 */ +void str_sanitize_cc(char *str_in) +{ + unsigned char *str = (unsigned char *)str_in; + while(*str) + { + if(*str < 32) + *str = ' '; + str++; + } +} + +/* makes sure that the string only contains the characters between 32 and 255 + \r\n\t */ +void str_sanitize(char *str_in) +{ + unsigned char *str = (unsigned char *)str_in; + while(*str) + { + if(*str < 32 && !(*str == '\r') && !(*str == '\n') && !(*str == '\t')) + *str = ' '; + str++; + } +} + +char *str_skip_to_whitespace(char *str) +{ + while(*str && (*str != ' ' && *str != '\t' && *str != '\n')) + str++; + return str; +} + +char *str_skip_whitespaces(char *str) +{ + while(*str && (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r')) + str++; + return str; +} + +/* case */ +int str_comp_nocase(const char *a, const char *b) +{ +#if defined(CONF_FAMILY_WINDOWS) + return _stricmp(a,b); +#else + return strcasecmp(a,b); +#endif +} + +int str_comp_nocase_num(const char *a, const char *b, const int num) +{ +#if defined(CONF_FAMILY_WINDOWS) + return _strnicmp(a, b, num); +#else + return strncasecmp(a, b, num); +#endif +} + +int str_comp(const char *a, const char *b) +{ + return strcmp(a, b); +} + +int str_comp_num(const char *a, const char *b, const int num) +{ + return strncmp(a, b, num); +} + +int str_comp_filenames(const char *a, const char *b) +{ + int result; + + for(; *a && *b; ++a, ++b) + { + if(*a >= '0' && *a <= '9' && *b >= '0' && *b <= '9') + { + result = 0; + do + { + if(!result) + result = *a - *b; + ++a; ++b; + } + while(*a >= '0' && *a <= '9' && *b >= '0' && *b <= '9'); + + if(*a >= '0' && *a <= '9') + return 1; + else if(*b >= '0' && *b <= '9') + return -1; + else if(result) + return result; + } + + if(tolower(*a) != tolower(*b)) + break; + } + return tolower(*a) - tolower(*b); +} + +const char *str_find_nocase(const char *haystack, const char *needle) +{ + while(*haystack) /* native implementation */ + { + const char *a = haystack; + const char *b = needle; + while(*a && *b && tolower(*a) == tolower(*b)) + { + a++; + b++; + } + if(!(*b)) + return haystack; + haystack++; + } + + return 0; +} + + +const char *str_find(const char *haystack, const char *needle) +{ + while(*haystack) /* native implementation */ + { + const char *a = haystack; + const char *b = needle; + while(*a && *b && *a == *b) + { + a++; + b++; + } + if(!(*b)) + return haystack; + haystack++; + } + + return 0; +} + +void str_hex(char *dst, int dst_size, const void *data, int data_size) +{ + static const char hex[] = "0123456789ABCDEF"; + int b; + + for(b = 0; b < data_size && b < dst_size/4-4; b++) + { + dst[b*3] = hex[((const unsigned char *)data)[b]>>4]; + dst[b*3+1] = hex[((const unsigned char *)data)[b]&0xf]; + dst[b*3+2] = ' '; + dst[b*3+3] = 0; + } +} + +void str_timestamp(char *buffer, int buffer_size) +{ + time_t time_data; + struct tm *time_info; + + time(&time_data); + time_info = localtime(&time_data); + strftime(buffer, buffer_size, "%Y-%m-%d_%H-%M-%S", time_info); + buffer[buffer_size-1] = 0; /* assure null termination */ +} + +int mem_comp(const void *a, const void *b, int size) +{ + return memcmp(a,b,size); +} + +const MEMSTATS *mem_stats() +{ + return &memory_stats; +} + +void net_stats(NETSTATS *stats_inout) +{ + *stats_inout = network_stats; +} + +void gui_messagebox(const char *title, const char *message) +{ +#if defined(CONF_PLATFORM_MACOSX) + DialogRef theItem; + DialogItemIndex itemIndex; + + /* FIXME: really needed? can we rely on glfw? */ + /* HACK - get events without a bundle */ + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + TransformProcessType(&psn,kProcessTransformToForegroundApplication); + SetFrontProcess(&psn); + /* END HACK */ + + CreateStandardAlert(kAlertStopAlert, + CFStringCreateWithCString(NULL, title, kCFStringEncodingASCII), + CFStringCreateWithCString(NULL, message, kCFStringEncodingASCII), + NULL, + &theItem); + + RunStandardAlert(theItem, NULL, &itemIndex); +#elif defined(CONF_FAMILY_UNIX) + static char cmd[1024]; + int err; + /* use xmessage which is available on nearly every X11 system */ + snprintf(cmd, sizeof(cmd), "xmessage -center -title '%s' '%s'", + title, + message); + + err = system(cmd); + dbg_msg("gui/msgbox", "result = %i", err); +#elif defined(CONF_FAMILY_WINDOWS) + MessageBox(NULL, + message, + title, + MB_ICONEXCLAMATION | MB_OK); +#else + /* this is not critical */ + #warning not implemented +#endif +} + +int str_isspace(char c) { return c == ' ' || c == '\n' || c == '\t'; } + +char str_uppercase(char c) +{ + if(c >= 'a' && c <= 'z') + return 'A' + (c-'a'); + return c; +} + +int str_toint(const char *str) { return atoi(str); } +float str_tofloat(const char *str) { return atof(str); } + + + +static int str_utf8_isstart(char c) +{ + if((c&0xC0) == 0x80) /* 10xxxxxx */ + return 0; + return 1; +} + +int str_utf8_rewind(const char *str, int cursor) +{ + while(cursor) + { + cursor--; + if(str_utf8_isstart(*(str + cursor))) + break; + } + return cursor; +} + +int str_utf8_forward(const char *str, int cursor) +{ + const char *buf = str + cursor; + if(!buf[0]) + return cursor; + + if((*buf&0x80) == 0x0) /* 0xxxxxxx */ + return cursor+1; + else if((*buf&0xE0) == 0xC0) /* 110xxxxx */ + { + if(!buf[1]) return cursor+1; + return cursor+2; + } + else if((*buf & 0xF0) == 0xE0) /* 1110xxxx */ + { + if(!buf[1]) return cursor+1; + if(!buf[2]) return cursor+2; + return cursor+3; + } + else if((*buf & 0xF8) == 0xF0) /* 11110xxx */ + { + if(!buf[1]) return cursor+1; + if(!buf[2]) return cursor+2; + if(!buf[3]) return cursor+3; + return cursor+4; + } + + /* invalid */ + return cursor+1; +} + +int str_utf8_encode(char *ptr, int chr) +{ + /* encode */ + if(chr <= 0x7F) + { + ptr[0] = (char)chr; + return 1; + } + else if(chr <= 0x7FF) + { + ptr[0] = 0xC0|((chr>>6)&0x1F); + ptr[1] = 0x80|(chr&0x3F); + return 2; + } + else if(chr <= 0xFFFF) + { + ptr[0] = 0xE0|((chr>>12)&0x0F); + ptr[1] = 0x80|((chr>>6)&0x3F); + ptr[2] = 0x80|(chr&0x3F); + return 3; + } + else if(chr <= 0x10FFFF) + { + ptr[0] = 0xF0|((chr>>18)&0x07); + ptr[1] = 0x80|((chr>>12)&0x3F); + ptr[2] = 0x80|((chr>>6)&0x3F); + ptr[3] = 0x80|(chr&0x3F); + return 4; + } + + return 0; +} + +int str_utf8_decode(const char **ptr) +{ + const char *buf = *ptr; + int ch = 0; + + do + { + if((*buf&0x80) == 0x0) /* 0xxxxxxx */ + { + ch = *buf; + buf++; + } + else if((*buf&0xE0) == 0xC0) /* 110xxxxx */ + { + ch = (*buf++ & 0x3F) << 6; if(!(*buf)) break; + ch += (*buf++ & 0x3F); + if(ch == 0) ch = -1; + } + else if((*buf & 0xF0) == 0xE0) /* 1110xxxx */ + { + ch = (*buf++ & 0x1F) << 12; if(!(*buf)) break; + ch += (*buf++ & 0x3F) << 6; if(!(*buf)) break; + ch += (*buf++ & 0x3F); + if(ch == 0) ch = -1; + } + else if((*buf & 0xF8) == 0xF0) /* 11110xxx */ + { + ch = (*buf++ & 0x0F) << 18; if(!(*buf)) break; + ch += (*buf++ & 0x3F) << 12; if(!(*buf)) break; + ch += (*buf++ & 0x3F) << 6; if(!(*buf)) break; + ch += (*buf++ & 0x3F); + if(ch == 0) ch = -1; + } + else + { + /* invalid */ + buf++; + break; + } + + *ptr = buf; + return ch; + } while(0); + + /* out of bounds */ + *ptr = buf; + return -1; + +} + +int str_utf8_check(const char *str) +{ + while(*str) + { + if((*str&0x80) == 0x0) + str++; + else if((*str&0xE0) == 0xC0 && (*(str+1)&0xC0) == 0x80) + str += 2; + else if((*str&0xF0) == 0xE0 && (*(str+1)&0xC0) == 0x80 && (*(str+2)&0xC0) == 0x80) + str += 3; + else if((*str&0xF8) == 0xF0 && (*(str+1)&0xC0) == 0x80 && (*(str+2)&0xC0) == 0x80 && (*(str+3)&0xC0) == 0x80) + str += 4; + else + return 0; + } + return 1; +} + + +unsigned str_quickhash(const char *str) +{ + unsigned hash = 5381; + for(; *str; str++) + hash = ((hash << 5) + hash) + (*str); /* hash * 33 + c */ + return hash; +} + + +#if defined(__cplusplus) +} +#endif diff --git a/web/css/dark.css b/web/css/dark.css new file mode 100644 index 0000000..bdcf3fa --- /dev/null +++ b/web/css/dark.css @@ -0,0 +1,49 @@ +body { background: #222 url('../img/dark.png'); color: #fff; } +.navbar { min-height: 40px; } +.navbar-brand { color: #FFF !important; padding: 10px; font-size: 20px; } +.dropdown .dropdown-toggle { padding-bottom: 10px; padding-top: 10px; } +.dropdown-menu > li > a { color: #FFF !important; background-color: #222222 !important; } +.dropdown-menu > li > a:hover { color: #FFF !important; background: #000 !important; } +.dropdown-menu { background: #222 !important; background-color: #222222 !important; } +.navbar-inverse .navbar-inner { background-color:#1B1B1B; background-image:-moz-linear-gradient(top, #222222, #111111); background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111)); background-image:-webkit-linear-gradient(top, #222222, #111111); background-image:-o-linear-gradient(top, #222222, #111111); background-image:linear-gradient(to bottom, #222222, #111111); background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0); border-color: #252525; } +.content { background: #222; padding: 20px; border-radius: 5px; border: 1px #000 solid; -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); box-shadow: 0 1px 10px rgba(0, 0, 0, .1); margin-bottom: 20px; } +.table { background: #000; margin-bottom: 0; border-collapse: collapse; border-radius: 3px; } +.table th { text-align: center; } +.table-striped tbody > tr.even > td, .table-striped tbody > tr.even > th { background-color: #2F2F2F; } +.table-striped tbody > tr.odd > td, .table-striped tbody > tr.odd > th { background-color: #000; } +.table td { text-align: center; border-color: #2F2F2F; } +.progress { margin-bottom: 0; background: #222; } +.table-hover > tbody > tr:hover > td { background: #414141; } +tr.even.expandRow > :hover { background: #2F2F2F !important; } +tr.odd.expandRow > :hover { background: #000 !important; } +.expandRow > td { padding: 0 !important; border-top: 0px !important; } +#cpu, #ram, #hdd, #network { min-width: 55px; max-width: 100px; } + +@media only screen and (max-width: 992px) { + #location, tr td:nth-child(5) { display:none; visibility:hidden; } +} +@media only screen and (max-width: 720px) { + #type, tr td:nth-child(4) { display:none; visibility:hidden; } + #location, tr td:nth-child(5) { display:none; visibility:hidden; } +} +@media only screen and (max-width: 600px) { + #type, tr td:nth-child(4) { display:none; visibility:hidden; } + #location, tr td:nth-child(5) { display:none; visibility:hidden; } + #uptime, tr td:nth-child(6) { display:none; visibility:hidden; } +} +@media only screen and (max-width: 533px) { + #type, tr td:nth-child(4) { display:none; visibility:hidden; } + #location, tr td:nth-child(5) { display:none; visibility:hidden; } + #uptime, tr td:nth-child(6) { display:none; visibility:hidden; } + #network, tr td:nth-child(8) { display:none; visibility:hidden; } +} +@media only screen and (max-width: 450px) { + body { font-size: 10px; } + .content { padding: 0; } + #name, tr td:nth-child(3) { min-width: 10px; max-width: 25px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } + #type, tr td:nth-child(4) { display:none; visibility:hidden; } + #location, tr td:nth-child(5) { display:none; visibility:hidden; } + #uptime, tr td:nth-child(6) { display:none; visibility:hidden; } + #network, tr td:nth-child(8) { display:none; visibility:hidden; } + #cpu, #ram, #hdd { min-width: 25px; max-width: 50px; } +} \ No newline at end of file diff --git a/web/css/light.css b/web/css/light.css new file mode 100644 index 0000000..5819497 --- /dev/null +++ b/web/css/light.css @@ -0,0 +1,46 @@ +body { background: #ebebeb url('../img/light.png'); } +.navbar { min-height: 40px; } +.navbar-brand { color: #fff; padding: 10px; font-size: 20px; } +.dropdown .dropdown-toggle { padding-bottom: 10px; padding-top: 10px; } +.navbar-inverse .navbar-brand { color: #fff; padding: 10px; font-size: 20px; } +.content { background: #ffffff; padding: 20px; border-radius: 5px; border: 1px #cecece solid; -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); box-shadow: 0 1px 10px rgba(0, 0, 0, .1); margin-bottom: 20px; } +.table { background: #ffffff; margin-bottom: 0; border-collapse: collapse; border-radius: 3px; } +.table th, .table td { text-align: center; } +.table-striped tbody > tr.even > td, .table-striped tbody > tr.even > th { background-color: #F9F9F9; } +.table-striped tbody > tr.odd > td, .table-striped tbody > tr.odd > th { background-color: #FFF; } +.progress { margin-bottom: 0; } +.progress-bar { color: #000; } +.table-hover > tbody > tr:hover > td { background: #E6E6E6; } +tr.even.expandRow > :hover { background: #F9F9F9 !important; } +tr.odd.expandRow > :hover { background: #FFF !important; } +.expandRow > td { padding: 0 !important; border-top: 0px !important; } +#cpu, #ram, #hdd, #network { min-width: 55px; max-width: 100px; } + +@media only screen and (max-width: 992px) { + #location, tr td:nth-child(5) { display:none; visibility:hidden; } +} +@media only screen and (max-width: 720px) { + #type, tr td:nth-child(4) { display:none; visibility:hidden; } + #location, tr td:nth-child(5) { display:none; visibility:hidden; } +} +@media only screen and (max-width: 600px) { + #type, tr td:nth-child(4) { display:none; visibility:hidden; } + #location, tr td:nth-child(5) { display:none; visibility:hidden; } + #uptime, tr td:nth-child(6) { display:none; visibility:hidden; } +} +@media only screen and (max-width: 533px) { + #type, tr td:nth-child(4) { display:none; visibility:hidden; } + #location, tr td:nth-child(5) { display:none; visibility:hidden; } + #uptime, tr td:nth-child(6) { display:none; visibility:hidden; } + #network, tr td:nth-child(8) { display:none; visibility:hidden; } +} +@media only screen and (max-width: 450px) { + body { font-size: 10px; } + .content { padding: 0; } + #name, tr td:nth-child(3) { min-width: 10px; max-width: 25px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } + #type, tr td:nth-child(4) { display:none; visibility:hidden; } + #location, tr td:nth-child(5) { display:none; visibility:hidden; } + #uptime, tr td:nth-child(6) { display:none; visibility:hidden; } + #network, tr td:nth-child(8) { display:none; visibility:hidden; } + #cpu, #ram, #hdd { min-width: 25px; max-width: 50px; } +} \ No newline at end of file diff --git a/web/favicon.ico b/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..157d27b8e398bd1c74ace81b0dc2d063c7462098 GIT binary patch literal 45470 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 bmwp|7vB_^V-)ii)ngOH7ycw`s%knP*N%2&K literal 0 HcmV?d00001 diff --git a/web/img/dark.png b/web/img/dark.png new file mode 100644 index 0000000000000000000000000000000000000000..20922820f5ee32b17a52548560388dbc5e2c5c94 GIT binary patch literal 600 zcmV-e0;m0nP)<h;3K|Lk000e1NJLTq001rk000*V0{{R3LlTW(0000jP)t-s5fKp# z3=9$y5)>2^5D*X!4h|C&6Aup$7Z(>56%`g177Yyz3kwSh3JM7c38-ifAOHXX=1D|B zRCwA=lUsr$AqWIfL<^zz{>R-ww`TUoSA}Vkbc?jsbu<&5t1W$|wV}QxLbi{VYj4Y( z`<!dh?jCQ-GXs7ziWlvrVog+*c+rRmbYESPc{F%09fQ`+JZIEnmqtWnYs=7iXjz=1 zpNW&q?EC4B^+%_0H?qmz(4F)0(-~bemyYS3CokRLVqk{1aoctJ<)_s#uImB#OUDSI zuv%ifQ}??3qv&mFKRDc>Wq08&8%9Lq<*SnC&26reul>-j;)mzOK*usBd~z4+s{{H3 zD>h_8R#wH+sWAAKse2^!mr1pY=7g9QZ-Rhz*OSi(^SM5HLr070t0mQ;oNx2Jiqsml z|Cf-?a<Yo=#tcuA1s;Ax=>F&&SgS_)psZ(#@&N&dUXXJ9$VWGMczStPj!hpyHPDA< zl*~{c(JqZ;n=T7GWgTAh1l@1gYVX@0`RFl`?7lTBs@~GXaAZeLm&shrznR>m^kj<b zAQ>xt7JFuzI^0K7(u6l$&=SMg64tmFQP(razaoluGO^R+;~))Jg0fxzQ-%K4f?}b` z?hV(Yr_ib$+*z^5!s##cv)T(StdAMgjy8k%GxG53^2yf<;{FDUu5LEynSYw#AlO}f mzkdv|erYfIv3Tu9Ab$btOBd6f7(Ds_0000<MNUMnLSTX`E)`P% literal 0 HcmV?d00001 diff --git a/web/img/light.png b/web/img/light.png new file mode 100644 index 0000000000000000000000000000000000000000..2f4febcb6297689992386cbd34f8b3cdbcbd52a5 GIT binary patch literal 19452 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%0AyVqkS<2a$W=)EmN|28yl>Y!Lx$gAkM} zHw=LW<8m)ZLyT}^%)PnJ4;5$U@v)xoC+hu;Wb1^Lun(`jxx8sTCBiwUuKUk<qMmZ2 z|Ke)>XFXz-`N6u~s*lttr|N_2=LrMXb8yi9xM6#i++WM<vf8Erzv;pV3dEBtOjion zgN=WZ=!H`t12?|*2Lp$#xjTj{oDPm@gv{W&lCSeax#+rZ&V{!6w<$Oj+&aKcM#mhu z>EwtUH>Y~Mz_ZVU_(wW`heO8IftO=AZEcv3$XEebaIhKI290L^3_<(q={42hz`jo1 z+v2UI;}OAkaI8O%Ju3rqHLgs{61RRPQmLt1^HUHLqR&%Cc#FSPJy{#yAj8!D6kJ71 zB-sngeN?xDMtQNOR3R^q>@yHUkgb5?KpWN1H?Czmt=`tMPuSm3=xYXJmB(MNmPj9& zNt`6G5&={M;5_a^yce5Q9&0u>iLN9oU0ET)-g;5caiYQYz=>lpgD_m9=UO>}(eny@ z!czm*?V2n5JVcgJ{Xo5cK-_K%r~Oc4i>+K7r97@XTx*V2k@2Fy7=2xB9iVGlOvz-< zau|vjSo2R^lA3~<*pUfO{+6UZM=Bkrf9Fx6`~3}u?!|w)Xj<r#B(~N0sEo}ViMJ_x znSXYDj{%H2$ju?r=v1JHo{#^l?T~HeR`stvwp&=9YRIC$SOtsZ@H$%}T{ySv&*i<Q zg&_9lS+^6|y5tCTUrOi)vAnkB_iMZa##{Xv8RTBx8J#PTcCe%UM*BDA%;+H7B>h|j zJe(C1azFw?%?+*dtQ&sjwnd45`c$FrBL~OnjCb<v8NQYsd3cAD?UBSs$V|!;`Ie@< z-zDOV(wOX<E{t_}OJe!M)J;>0KPr{;vS;wKL+G&O+wzLbx$J&DxUto;0;r(>)+yI} zUPz5gOdnLUI_-z<+vvR9_mkEE)_J*WcbCcPy8onGyN3M3<k5Wp!hzZ9z#_Y&37p?0 z%=A%}Wqn``w~HGT9TqC0JsPCdpLD#oE-+0ZCRp2*v+dDD4S-UbIN_oLsl*$rn!)b7 z%SLrcbat1N6Ji<-osn{JmHhrJLMw>Lf0-os6Mq!s{ZpnGhg>Z^Qnmh4@m;&zuNF@Z zhV5cg)$LN_3O|e`Wmv!|-dgKJ+a_7gyEFr<;yTd%F8_2w!UxLs^7Ly2ai;|wIHqlS zIl)d#wYbFiBy$CMRDVB>t{aY2U0ip^Cyw#E&9g?WB3dU_XNUXG6FQ>}mw@U}#;+5) z=+ea|YNP{^-1=0F8)kv2`y`A=g?bPWSLL}gHupBa?>*E+@yU60l0UV_ghP@3)8@pm zXp?F*q`(qbAO<(;47Dp*$-cJDD{WvaRx}V&7>BF;Xm;ypw9_bqf1cxYbwvx5%d}{L zxOq9ewtpr4gN)~fKb!;}_Tf5Ze@oBgH2W1w02A-{E?wop{NS4ZYy8_@_@lAy&Dc<) zcgS^@vo|0AKf$FY!pq(thG(B9ZLcK-Bu7TH|3YykWABBy?5Wr+t8MIa5_>K2vVkWZ zc-gE-8}=n2GP;2wR%~*wwd)mwUQzbg#+I$Q8k8-Nf1pRG<)DsjUXD_fQ$_xEF*WB! zj|Mg!Cn@2hVF53x=ugvmz3Nt>W*#3er9)Rg*nViK3_H(dS4Ta(1y#ggfF*=h5tu<g z{{&ERm?-t6k;^$@9ZppCxK}wh8*9g!D(t!6JDbe4*wUmpK%TJN7~CQ={ZqAzKXAQU zJlYb>Rt~lPoBXR?9#HMJ!hGoM5~QznXD-p|KxE}Q>wgtygcG?-)(DsD={5Zy`t`{^ T36JFk00000NkvXXu0mjfh_8PN literal 0 HcmV?d00001 diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..6cb0ed6 --- /dev/null +++ b/web/index.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<!-- + json字段保持完整, 后期更新会向下兼容 + 可以自定义前端展示 + ლ(•̀ _ •́ ლ) + ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ) + ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ) + ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ) + ლ(•̀ _ •́ ლ) +--> +<html> + <head> + <title>云监控</title> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="云监控"> + <meta name="author" content="BotoX"> + <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css"> + <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap-theme.min.css"> + <link rel="stylesheet" href="css/dark.css" title="dark"> + <link rel="stylesheet" href="css/light.css" title="light"> + <style> + body { + padding-top: 70px; + padding-bottom: 30px; + } + </style> + <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> + <![endif]--> + </head> + <body> + <div role="navigation" class="navbar navbar-inverse navbar-fixed-top"> + <div class="navbar-inner"> + <div class="container"> + <div class="navbar-header"> + <button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button"> + <span class="sr-only">Toggle navigation</span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + <a href="#" class="navbar-brand">云监控</a> + </div> + <div class="navbar-collapse collapse"> + <ul class="nav navbar-nav"> + <li class="dropdown"> + <a data-toggle="dropdown" class="dropdown-toggle" href="#">风格<b class="caret"></b></a> + <ul class="dropdown-menu"> + <li><a href="#" onclick="setActiveStyleSheet('dark')">黑夜</a></li> + <li><a href="#" onclick="setActiveStyleSheet('light')">白天</a></li> + </ul> + </li> + </ul> + </div><!--/.nav-collapse --> + </div> + </div> + </div> + + <div class="container content"> + <div id="loading-notice"> + <noscript> + <div class="alert alert-danger" style="text-align: center;"> + <strong>Enable JavaScript</strong> you fucking autist neckbeard, it's not gonna hurt you. + </div> + </noscript> + <div class="progress progress-striped active"> + <div class="progress-bar progress-bar-warning" style="width: 100%;">加载中...</div> + </div> + <div style="text-align: center;"> + 如果出现此消息,请确保您已启用Javascript! <br />否则云监控主服务没启动或已关闭. + </div> + <p></p> + </div> + <table class="table table-striped table-condensed table-hover"> + <thead> + <tr> + <th id="status4" style="text-align: center;">IPv4</th> + <th id="ipstatus" style="text-align: center;">Flight</th> + <th id="name">节点名</th> + <th id="type">虚拟化</th> + <th id="location">位置</th> + <th id="uptime">在线时间</th> + <th id="load">负载</th> + <th id="network">网络 ↓|↑</th> + <th id="traffic">流量 ↓|↑</th> + <th id="cpu">处理器</th> + <th id="ram">内存</th> + <th id="hdd">硬盘</th> + </tr> + </thead> + <tbody id="servers"> + <!-- Servers here \o/ --> + </tbody> + </table> + <br /> + <div id="updated">Updating...</div> + </div> + + <div class="container"> + <p style="text-align: center; font-size: 10px;"> + <a href="https://github.com/cppla/ServerStatus">ServerStatus中文版</a> + </p> + </div> + <script src="//code.jquery.com/jquery-1.10.2.min.js"></script> + <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script> + <script src="js/serverstatus.js"></script> + </body> +</html> diff --git a/web/js/serverstatus.js b/web/js/serverstatus.js new file mode 100644 index 0000000..dc962b7 --- /dev/null +++ b/web/js/serverstatus.js @@ -0,0 +1,393 @@ +// serverstatus.js +var error = 0; +var d = 0; +var server_status = new Array(); + +function timeSince(date) { + if(date == 0) + return "从未."; + + var seconds = Math.floor((new Date() - date) / 1000); + var interval = Math.floor(seconds / 31536000); + + if (interval > 1) + return interval + " 年前."; + interval = Math.floor(seconds / 2592000); + if (interval > 1) + return interval + " 月前."; + interval = Math.floor(seconds / 86400); + if (interval > 1) + return interval + " 日前."; + interval = Math.floor(seconds / 3600); + if (interval > 1) + return interval + " 小时前."; + interval = Math.floor(seconds / 60); + if (interval > 1) + return interval + " 分钟前."; + /*if(Math.floor(seconds) >= 5) + return Math.floor(seconds) + " seconds";*/ + else + return "几秒前."; +} + +function bytesToSize(bytes, precision, si) +{ + var ret; + si = typeof si !== 'undefined' ? si : 0; + if(si != 0) { + var kilobyte = 1000; + var megabyte = kilobyte * 1000; + var gigabyte = megabyte * 1000; + var terabyte = gigabyte * 1000; + } else { + var kilobyte = 1024; + var megabyte = kilobyte * 1024; + var gigabyte = megabyte * 1024; + var terabyte = gigabyte * 1024; + } + + if ((bytes >= 0) && (bytes < kilobyte)) { + return bytes + ' B'; + + } else if ((bytes >= kilobyte) && (bytes < megabyte)) { + ret = (bytes / kilobyte).toFixed(precision) + ' K'; + + } else if ((bytes >= megabyte) && (bytes < gigabyte)) { + ret = (bytes / megabyte).toFixed(precision) + ' M'; + + } else if ((bytes >= gigabyte) && (bytes < terabyte)) { + ret = (bytes / gigabyte).toFixed(precision) + ' G'; + + } else if (bytes >= terabyte) { + ret = (bytes / terabyte).toFixed(precision) + ' T'; + + } else { + return bytes + ' B'; + } + if(si != 0) { + return ret + 'B'; + } else { + return ret + 'iB'; + } +} + +function uptime() { + $.getJSON("json/stats.json", function(result) { + $("#loading-notice").remove(); + if(result.reload) + setTimeout(function() { location.reload(true) }, 1000); + + for (var i = 0; i < result.servers.length; i++) { + var TableRow = $("#servers tr#r" + i); + var ExpandRow = $("#servers #rt" + i); + var hack; // fuck CSS for making me do this + if(i%2) hack="odd"; else hack="even"; + if (!TableRow.length) { + $("#servers").append( + "<tr id=\"r" + i + "\" data-toggle=\"collapse\" data-target=\"#rt" + i + "\" class=\"accordion-toggle " + hack + "\">" + + "<td id=\"online4\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" + + "<td id=\"ip_status\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" + + "<td id=\"name\">加载中</td>" + + "<td id=\"type\">加载中</td>" + + "<td id=\"location\">加载中</td>" + + "<td id=\"uptime\">加载中</td>" + + "<td id=\"load\">加载中</td>" + + "<td id=\"network\">加载中</td>" + + "<td id=\"traffic\">加载中</td>" + + "<td id=\"cpu\"><div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" + + "<td id=\"memory\"><div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" + + "<td id=\"hdd\"><div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" + + "</tr>" + + "<tr class=\"expandRow " + hack + "\"><td colspan=\"12\"><div class=\"accordian-body collapse\" id=\"rt" + i + "\">" + + "<div id=\"expand_mem\">加载中</div>" + + "<div id=\"expand_swap\">加载中</div>" + + "<div id=\"expand_hdd\">加载中</div>" + + "<div id=\"expand_custom\">加载中</div>" + + "</div></td></tr>" + ); + TableRow = $("#servers tr#r" + i); + ExpandRow = $("#servers #rt" + i); + server_status[i] = true; + } + TableRow = TableRow[0]; + if(error) { + TableRow.setAttribute("data-target", "#rt" + i); + server_status[i] = true; + } + + // Online4 + if (result.servers[i].online4) { + TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-success"; + TableRow.children["online4"].children[0].children[0].innerHTML = "<small>开启</small>"; + } else { + TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-danger"; + TableRow.children["online4"].children[0].children[0].innerHTML = "<small>关闭</small>"; + } + + // Online6 + //if (result.servers[i].online6) { + // TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-success"; + // TableRow.children["online6"].children[0].children[0].innerHTML = "<small>开启</small>"; + //} else { + // TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-danger"; + // TableRow.children["online6"].children[0].children[0].innerHTML = "<small>关闭</small>"; + //} + + // Ipstatus + if (result.servers[i].ip_status) { + TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-success"; + TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>MH361</small>"; + } else { + TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-danger"; + TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>MH370</small>"; + } + + // Name + TableRow.children["name"].innerHTML = result.servers[i].name; + + // Type + TableRow.children["type"].innerHTML = result.servers[i].type; + + // Location + TableRow.children["location"].innerHTML = result.servers[i].location; + if (!result.servers[i].online4 && !result.servers[i].online6) { + if (server_status[i]) { + TableRow.children["uptime"].innerHTML = "–"; + TableRow.children["load"].innerHTML = "–"; + TableRow.children["network"].innerHTML = "–"; + TableRow.children["traffic"].innerHTML = "–"; + TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger"; + TableRow.children["cpu"].children[0].children[0].style.width = "100%"; + TableRow.children["cpu"].children[0].children[0].innerHTML = "<small>关闭</small>"; + TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger"; + TableRow.children["memory"].children[0].children[0].style.width = "100%"; + TableRow.children["memory"].children[0].children[0].innerHTML = "<small>关闭</small>"; + TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger"; + TableRow.children["hdd"].children[0].children[0].style.width = "100%"; + TableRow.children["hdd"].children[0].children[0].innerHTML = "<small>关闭</small>"; + if(ExpandRow.hasClass("in")) { + ExpandRow.collapse("hide"); + } + TableRow.setAttribute("data-target", ""); + server_status[i] = false; + } + } else { + if (!server_status[i]) { + TableRow.setAttribute("data-target", "#rt" + i); + server_status[i] = true; + } + + // Uptime + TableRow.children["uptime"].innerHTML = result.servers[i].uptime; + + // Load + if(result.servers[i].load == -1) { + TableRow.children["load"].innerHTML = "–"; + } else { + var loadstr = "" + loadstr += result.servers[i].load_1.toFixed(2); + loadstr += " | " + loadstr += result.servers[i].load_5.toFixed(2); + loadstr += " | " + loadstr += result.servers[i].load_15.toFixed(2); + TableRow.children["load"].innerHTML = loadstr + } + + // Network + var netstr = ""; + if(result.servers[i].network_rx < 1000) + netstr += result.servers[i].network_rx.toFixed(0) + "B"; + else if(result.servers[i].network_rx < 1000*1000) + netstr += (result.servers[i].network_rx/1000).toFixed(0) + "K"; + else + netstr += (result.servers[i].network_rx/1000/1000).toFixed(1) + "M"; + netstr += " | " + if(result.servers[i].network_tx < 1000) + netstr += result.servers[i].network_tx.toFixed(0) + "B"; + else if(result.servers[i].network_tx < 1000*1000) + netstr += (result.servers[i].network_tx/1000).toFixed(0) + "K"; + else + netstr += (result.servers[i].network_tx/1000/1000).toFixed(1) + "M"; + TableRow.children["network"].innerHTML = netstr; + + //Traffic + var trafficstr = ""; + if(result.servers[i].network_in < 1024) + trafficstr += result.servers[i].network_in.toFixed(0) + "B"; + else if(result.servers[i].network_in < 1024*1024) + trafficstr += (result.servers[i].network_in/1024).toFixed(0) + "K"; + else if(result.servers[i].network_in < 1024*1024*1024) + trafficstr += (result.servers[i].network_in/1024/1024).toFixed(1) + "M"; + else + trafficstr += (result.servers[i].network_in/1024/1024/1024).toFixed(2) + "G"; + trafficstr += " | " + if(result.servers[i].network_out < 1024) + trafficstr += result.servers[i].network_out.toFixed(0) + "B"; + else if(result.servers[i].network_out < 1024*1024) + trafficstr += (result.servers[i].network_out/1024).toFixed(0) + "K"; + else if(result.servers[i].network_out < 1024*1024*1024) + trafficstr += (result.servers[i].network_out/1024/1024).toFixed(1) + "M"; + else + trafficstr += (result.servers[i].network_out/1024/1024/1024).toFixed(2) + "G"; + TableRow.children["traffic"].innerHTML = trafficstr; + + // CPU + if (result.servers[i].cpu >= 90) + TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger"; + else if (result.servers[i].cpu >= 80) + TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-warning"; + else + TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-success"; + TableRow.children["cpu"].children[0].children[0].style.width = result.servers[i].cpu + "%"; + TableRow.children["cpu"].children[0].children[0].innerHTML = result.servers[i].cpu + "%"; + + // Memory + var Mem = ((result.servers[i].memory_used/result.servers[i].memory_total)*100.0).toFixed(0); + if (Mem >= 90) + TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger"; + else if (Mem >= 80) + TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-warning"; + else + TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-success"; + TableRow.children["memory"].children[0].children[0].style.width = Mem + "%"; + TableRow.children["memory"].children[0].children[0].innerHTML = Mem + "%"; + ExpandRow[0].children["expand_mem"].innerHTML = "内存: " + bytesToSize(result.servers[i].memory_used*1024, 2) + " / " + bytesToSize(result.servers[i].memory_total*1024, 2); + // Swap + ExpandRow[0].children["expand_swap"].innerHTML = "交换分区: " + bytesToSize(result.servers[i].swap_used*1024, 2) + " / " + bytesToSize(result.servers[i].swap_total*1024, 2); + + // HDD + var HDD = ((result.servers[i].hdd_used/result.servers[i].hdd_total)*100.0).toFixed(0); + if (HDD >= 90) + TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger"; + else if (HDD >= 80) + TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-warning"; + else + TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-success"; + TableRow.children["hdd"].children[0].children[0].style.width = HDD + "%"; + TableRow.children["hdd"].children[0].children[0].innerHTML = HDD + "%"; + ExpandRow[0].children["expand_hdd"].innerHTML = "硬盘: " + bytesToSize(result.servers[i].hdd_used*1024*1024, 2) + " / " + bytesToSize(result.servers[i].hdd_total*1024*1024, 2); + + // Custom + if (result.servers[i].custom) { + ExpandRow[0].children["expand_custom"].innerHTML = result.servers[i].custom + } else { + ExpandRow[0].children["expand_custom"].innerHTML = "" + } + } + }; + + d = new Date(result.updated*1000); + error = 0; + }).fail(function(update_error) { + if (!error) { + $("#servers > tr.accordion-toggle").each(function(i) { + var TableRow = $("#servers tr#r" + i)[0]; + var ExpandRow = $("#servers #rt" + i); + TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-error"; + TableRow.children["online4"].children[0].children[0].innerHTML = "<small>错误</small>"; + //TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-error"; + //TableRow.children["online6"].children[0].children[0].innerHTML = "<small>错误</small>"; + TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-error"; + TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>错误</small>"; + TableRow.children["uptime"].innerHTML = "<div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-error\"><small>错误</small></div></div>"; + TableRow.children["load"].innerHTML = "<div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-error\"><small>错误</small></div></div>"; + TableRow.children["network"].innerHTML = "<div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-error\"><small>错误</small></div></div>"; + TableRow.children["traffic"].innerHTML = "<div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-error\"><small>错误</small></div></div>"; + TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-error"; + TableRow.children["cpu"].children[0].children[0].style.width = "100%"; + TableRow.children["cpu"].children[0].children[0].innerHTML = "<small>错误</small>"; + TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-error"; + TableRow.children["memory"].children[0].children[0].style.width = "100%"; + TableRow.children["memory"].children[0].children[0].innerHTML = "<small>错误</small>"; + TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-error"; + TableRow.children["hdd"].children[0].children[0].style.width = "100%"; + TableRow.children["hdd"].children[0].children[0].innerHTML = "<small>错误</small>"; + if(ExpandRow.hasClass("in")) { + ExpandRow.collapse("hide"); + } + TableRow.setAttribute("data-target", ""); + server_status[i] = false; + }); + } + error = 1; + $("#updated").html("更新错误."); + }); +} + +function updateTime() { + if (!error) + $("#updated").html("最后更新: " + timeSince(d)); +} + +uptime(); +updateTime(); +setInterval(uptime, 2000); +setInterval(updateTime, 500); + + +// styleswitcher.js +function setActiveStyleSheet(title) { + var i, a, main; + for(i=0; (a = document.getElementsByTagName("link")[i]); i++) { + if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) { + a.disabled = true; + if(a.getAttribute("title") == title) a.disabled = false; + } + } +} + +function getActiveStyleSheet() { + var i, a; + for(i=0; (a = document.getElementsByTagName("link")[i]); i++) { + if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && !a.disabled) + return a.getAttribute("title"); + } + return null; +} + +function getPreferredStyleSheet() { + var i, a; + for(i=0; (a = document.getElementsByTagName("link")[i]); i++) { + if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("rel").indexOf("alt") == -1 && a.getAttribute("title")) + return a.getAttribute("title"); + } +return null; +} + +function createCookie(name,value,days) { + if (days) { + var date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + } + else expires = ""; + document.cookie = name+"="+value+expires+"; path=/"; +} + +function readCookie(name) { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') + c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) + return c.substring(nameEQ.length,c.length); + } + return null; +} + +window.onload = function(e) { + var cookie = readCookie("style"); + var title = cookie ? cookie : getPreferredStyleSheet(); + setActiveStyleSheet(title); +} + +window.onunload = function(e) { + var title = getActiveStyleSheet(); + createCookie("style", title, 365); +} + +var cookie = readCookie("style"); +var title = cookie ? cookie : getPreferredStyleSheet(); +setActiveStyleSheet(title); diff --git a/web/json/.gitignore b/web/json/.gitignore new file mode 100644 index 0000000..54c8f20 --- /dev/null +++ b/web/json/.gitignore @@ -0,0 +1,2 @@ +stats.json +stats.json~ \ No newline at end of file