diff --git a/.gitignore b/.gitignore
index ea8c4bf7f35f6f77f75d92ad8ce8349f6e81ddba..b0d62291848d1dd3ec174f7e470ee7216915ca88 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
 /target
+*.sqlite
+*.db
diff --git a/Cargo.lock b/Cargo.lock
index ff528f3d167396d7eec94fb425658e5c31e871c6..09c13291d19d44b0a2edc753607d9006781c7d1b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -148,6 +148,19 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
 [[package]]
 name = "cookie"
 version = "0.11.3"
@@ -159,7 +172,7 @@ dependencies = [
  "hkdf",
  "hmac",
  "percent-encoding 2.1.0",
- "rand",
+ "rand 0.7.3",
  "sha2",
  "time",
 ]
@@ -221,11 +234,26 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
 
+[[package]]
+name = "fallible-iterator"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
+
+[[package]]
+name = "fallible-streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
+
 [[package]]
 name = "ffddns-web"
 version = "0.1.0"
 dependencies = [
+ "chrono",
+ "rand 0.8.3",
  "rocket",
+ "rusqlite",
 ]
 
 [[package]]
@@ -248,6 +276,17 @@ dependencies = [
  "wasi 0.9.0+wasi-snapshot-preview1",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+]
+
 [[package]]
 name = "ghash"
 version = "0.2.3"
@@ -344,6 +383,12 @@ dependencies = [
  "hashbrown",
 ]
 
+[[package]]
+name = "itoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+
 [[package]]
 name = "language-tags"
 version = "0.2.2"
@@ -356,6 +401,22 @@ version = "0.2.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
 
+[[package]]
+name = "libsqlite3-sys"
+version = "0.17.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56d90181c2904c287e5390186be820e5ef311a3c62edebb7d6ca3d6a48ce041d"
+dependencies = [
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
 [[package]]
 name = "log"
 version = "0.3.9"
@@ -374,6 +435,15 @@ dependencies = [
  "cfg-if 1.0.0",
 ]
 
+[[package]]
+name = "lru-cache"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+dependencies = [
+ "linked-hash-map",
+]
+
 [[package]]
 name = "matches"
 version = "0.1.8"
@@ -395,6 +465,25 @@ dependencies = [
  "log 0.3.9",
 ]
 
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "num_cpus"
 version = "1.13.0"
@@ -445,6 +534,12 @@ version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
 
+[[package]]
+name = "pkg-config"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+
 [[package]]
 name = "polyval"
 version = "0.3.3"
@@ -485,11 +580,23 @@ version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
 dependencies = [
- "getrandom",
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc 0.2.0",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+dependencies = [
  "libc",
- "rand_chacha",
- "rand_core",
- "rand_hc",
+ "rand_chacha 0.3.0",
+ "rand_core 0.6.1",
+ "rand_hc 0.3.0",
 ]
 
 [[package]]
@@ -499,7 +606,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
 dependencies = [
  "ppv-lite86",
- "rand_core",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.1",
 ]
 
 [[package]]
@@ -508,7 +625,16 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
 dependencies = [
- "getrandom",
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5"
+dependencies = [
+ "getrandom 0.2.2",
 ]
 
 [[package]]
@@ -517,7 +643,16 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
 dependencies = [
- "rand_core",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+dependencies = [
+ "rand_core 0.6.1",
 ]
 
 [[package]]
@@ -573,6 +708,29 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "rusqlite"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64a656821bb6317a84b257737b7934f79c0dbb7eb694710475908280ebad3e64"
+dependencies = [
+ "bitflags",
+ "chrono",
+ "fallible-iterator",
+ "fallible-streaming-iterator",
+ "libsqlite3-sys",
+ "lru-cache",
+ "memchr",
+ "serde_json",
+ "time",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
 [[package]]
 name = "safemem"
 version = "0.3.3"
@@ -585,6 +743,17 @@ version = "1.0.123"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
 
+[[package]]
+name = "serde_json"
+version = "1.0.62"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
 [[package]]
 name = "sha2"
 version = "0.8.2"
@@ -739,6 +908,12 @@ dependencies = [
  "percent-encoding 1.0.1",
 ]
 
+[[package]]
+name = "vcpkg"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
+
 [[package]]
 name = "version_check"
 version = "0.1.5"
diff --git a/README.md b/README.md
index b80f793ca07630f71e1b94662a1a0bc4361b7d0d..605bc9c9366159025977bb2006153719e9a3fae4 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,9 @@ Daten speichern müssen. Ein paar Stichpunkte:
 	- Token wird gelöscht
 	- Domain kann wieder registriert werden
 
+Um Spam zu vermeiden sollten wir ein captcha in irgendeiner Form nutzen.
+
+
 Über das Token könnte man auch zusätzliche Dinge realisieren, die Authentifizierung benötigen.
 Z.B. ein simples Webinterface um einzusehen, wann die Domain zuletzt geupdatet wurde oder um sie
 manuell freizugeben.
diff --git a/ffddns-web/Cargo.toml b/ffddns-web/Cargo.toml
index d4d708ce9d9a938ecad2b2f4eb39a9ceb4c63632..50459fd7211b8006638b6ad0595009385d9bc2e7 100644
--- a/ffddns-web/Cargo.toml
+++ b/ffddns-web/Cargo.toml
@@ -8,3 +8,6 @@ edition = "2018"
 
 [dependencies]
 rocket = "0.4.7"
+rusqlite = {version = "0.21", features = ["chrono", "serde_json"]}
+chrono = "0.4.19"
+rand = "0.8.3"
diff --git a/ffddns-web/src/db.rs b/ffddns-web/src/db.rs
new file mode 100644
index 0000000000000000000000000000000000000000..27c7f17b57eb5b135e650b73f341faf119e6b22d
--- /dev/null
+++ b/ffddns-web/src/db.rs
@@ -0,0 +1,104 @@
+use rusqlite as sqlite;
+use sqlite::params;
+use std::path::PathBuf;
+use chrono::{Utc, DateTime};
+use std::net::{Ipv4Addr, Ipv6Addr};
+
+
+pub struct Database {
+	conn: sqlite::Connection,
+}
+
+
+unsafe impl Send for Database {}
+unsafe impl Sync for Database {}
+
+
+impl Database {
+	pub fn new(path: PathBuf) -> Self {
+		let conn: sqlite::Connection = sqlite::Connection::open(&path).unwrap();
+		conn.execute_batch(include_str!("init.sql")).unwrap();
+		Database { conn }
+	}
+
+	pub fn get_all_domains(&self) -> Vec<Domain> {
+		let mut stmt: sqlite::Statement = self.conn.prepare("SELECT * FROM domains").unwrap();
+
+		stmt.query_map(
+			params![],
+			|row| Ok(Domain::from_row(row))
+		).unwrap().map(|x| x.unwrap()).collect()
+	}
+
+	pub fn insert_new_domain(&self, d: &Domain) {
+		self.conn.execute(
+			"INSERT INTO domains VALUES ($1, $2, $3, $4, $5)",
+			params![d.domainname, d.token, d.lastupdate, d.ipv4.map(|x| x.to_string()), d.ipv6.map(|x| x.to_string())]
+		).unwrap();
+	}
+
+
+	pub fn get_domain(&self, domain: &String) -> Option<Domain> {
+		let r: sqlite::Result<_> = self.conn.query_row_and_then(
+			"SELECT * FROM domains WHERE domainname=$1",
+			params![domain],
+			|row| Ok(Domain::from_row(row))
+		);
+
+		match r {
+			Err(_) => None,
+			Ok(o) => Some(o)
+		}
+	}
+
+	pub fn remove_domain(&self, d: String) {
+		self.conn.execute(
+			"DELETE FROM domains WHERE domainname=$1",
+			params![d]
+		).unwrap();
+	}
+
+
+	pub fn update_lastupdate(&self, d: &String, lastupdate: DateTime<Utc>) {
+		self.conn.execute(
+			"UPDATE domains SET lastupdate=$2 WHERE domainname=$1",
+			params![d, lastupdate]
+		).unwrap();
+	}
+
+	pub fn update_ipv4(&self, d: &String, addr: Ipv4Addr) {
+		self.conn.execute(
+			"UPDATE domains SET ipv4=$2 WHERE domainname=$1",
+			params![d, addr.to_string()]
+		).unwrap();
+	}
+
+	pub fn update_ipv6(&self, d: &String, addr: Ipv6Addr) {
+		self.conn.execute(
+			"UPDATE domains SET ipv6=$2 WHERE domainname=$1",
+			params![d, addr.to_string()]
+		).unwrap();
+	}
+}
+
+
+#[derive(Debug, Clone)]
+pub struct Domain {
+	pub domainname: String,
+	pub token: String,
+	pub lastupdate: Option<DateTime<Utc>>,
+	pub ipv4: Option<Ipv4Addr>,
+	pub ipv6: Option<Ipv6Addr>,
+}
+
+impl Domain {
+	fn from_row(row: &sqlite::Row) -> Self {
+		Self {
+			domainname: row.get("domainname").unwrap(),
+			token: row.get("token").unwrap(),
+			lastupdate: row.get("lastupdate").unwrap(),
+			ipv4: row.get::<_, Option<String>>("ipv4").unwrap().map(|x| x.parse().unwrap()),
+			ipv6: row.get::<_, Option<String>>("ipv6").unwrap().map(|x| x.parse().unwrap()),
+		}
+	}
+}
diff --git a/ffddns-web/src/init.sql b/ffddns-web/src/init.sql
new file mode 100644
index 0000000000000000000000000000000000000000..f02ddb0444552da0441a1e66e38cb249bed51a4a
--- /dev/null
+++ b/ffddns-web/src/init.sql
@@ -0,0 +1,7 @@
+CREATE TABLE IF NOT EXISTS domains (
+	domainname TEXT PRIMARY KEY,
+	token TEXT NOT NULL,
+	lastupdate TEXT,
+	ipv4 TEXT,
+	ipv6 TEXT
+);
diff --git a/ffddns-web/src/main.rs b/ffddns-web/src/main.rs
index aac2ed8d209d2e8e5df6e8587cdcc6f9985584d6..b7f3c6966078edfb78c7f02ace9337ff5ba0f803 100644
--- a/ffddns-web/src/main.rs
+++ b/ffddns-web/src/main.rs
@@ -1,17 +1,145 @@
 #![feature(proc_macro_hygiene, decl_macro)]
 
-use rocket;
-use rocket::get;
-use rocket::routes;
+mod db;
+use chrono::{DateTime, Utc};
+use db::{
+    Database,
+    Domain,
+};
+use rocket::{
+    self,
+    get,
+    post,
+    routes,
+    State,
+};
+use rocket::request::{
+    Request,
+    FromRequest,
+    Outcome,
+};
+use std::fmt::{self, Display};
+use std::net::IpAddr;
+use rand;
 
-#[get("/update?<key>&<address>")]
-fn index(key: String, address: Option<String>) -> String {
-    format!("{}: updating ip address to {:?}", key, address)
+
+pub struct ClientIp(IpAddr);
+
+
+impl ClientIp {
+    pub fn inner(&self) -> &IpAddr {
+        let ClientIp(ip) = self;
+        ip
+    }
+    pub fn into_inner(self) -> IpAddr {
+        let ClientIp(ip) = self;
+        ip
+    }
+}
+
+
+impl Display for ClientIp {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.inner().to_string())
+    }
+}
+
+
+impl<'a, 'r> FromRequest<'a, 'r> for ClientIp {
+    type Error = String;
+
+    fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
+        let ip = request.client_ip().unwrap();
+        Outcome::Success(ClientIp(ip))
+    }
+}
+
+
+#[derive(Debug, Clone)]
+pub struct DomainUpdate {
+    domain: String,
+    ip: IpAddr
+}
+
+
+#[get("/update?<token>&<domain>&<ip>")]
+fn update(db: State<Database>, clientip: ClientIp, token: String, domain: String, ip: Option<String>) -> String {
+    let new_ip: IpAddr = {
+        if let Some(iip) = ip {
+            iip.parse::<IpAddr>().unwrap()
+        }
+        else {
+            clientip.into_inner()
+        }
+    };
+
+    let d = db.get_domain(&domain).unwrap();
+
+    if d.token != token {
+        return "not a valid token".to_string();
+    }
+
+    match new_ip {
+        IpAddr::V4(addr) => db.update_ipv4(&domain, addr),
+        IpAddr::V6(addr) => db.update_ipv6(&domain, addr),
+    }
+
+    db.update_lastupdate(&domain, Utc::now());
+
+    format!("{} updated to {:?}", domain, new_ip)
+}
+
+
+#[get("/create?<domain>")]
+fn create(db: State<Database>, domain: String) -> String {
+    let token = generate_token();
+    let d = Domain {
+        domainname: domain.clone(),
+        token: token.clone(),
+        lastupdate: None,
+        ipv4: None,
+        ipv6: None
+    };
+
+    db.insert_new_domain(&d);
+
+    format!("your token for {}: {}", domain, token)
 }
 
 
+#[get("/status?<domain>")]
+fn status(db: State<Database>, domain: String) -> String {
+    let domaininfo = match db.get_domain(&domain) {
+        None => return "domain not found".to_string(),
+        Some(r) => r,
+    };
+
+    format!("{:#?}", domaininfo)
+}
+
+
+
 fn main() {
+    let db = db::Database::new("./ffddns.sqlite".into());
+
     rocket::ignite()
-        .mount("/", routes![index])
+        .mount("/", routes![
+            update,
+            create,
+            status
+        ])
+        .manage(db)
         .launch();
 }
+
+
+
+fn generate_token() -> String {
+    let mut token = String::new();
+
+    for _ in 0..8 {
+        token.push_str(&format!("{:02x}", rand::random::<u8>()));
+    }
+
+    token
+}