-
-### Characteristics
-1. End to end encryption;
-2. Supported tor connections;
-3. Symmetric algorithm: AES-CBC;
-4. Asymmetric algorithm: RSA-OAEP, RSA-PSS;
-5. Hash function: SHA256;
-
-### Account page
-
-
-### Compile and run
-```
-$ make
-> go build client.go gconsts.go cdatabase.go cmodels.go csessions.go
-> go build server.go gconsts.go sdatabase.go sconfig.go
-$ ./server -open="localhost:8080" &
-$ ./client -open="localhost:7545"
-```
-
-### List of emails page
-
-
-### DB and CFG files
-> Database and config files are creates when the application starts.
-
-#### Server side db (server.db)
-```sql
-/* recv = hash(public_key) */
-/* hash = hash(data) */
-/* data = encrypt(email) */
-CREATE TABLE IF NOT EXISTS emails (
- id INTEGER,
- recv VARCHAR(255),
- hash VARCHAR(255) UNIQUE,
- data TEXT,
- addtime DATETIME DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY(id)
-);
-```
-
-#### Server side cfg (server.cfg)
-```go
-type CFG struct {
- Pasw string `json:"pasw"`
- Conns [][2]string `json:"conns"`
-}
-```
-
-#### Client side db (client.db)
-```sql
-/* !key_pasw = hash(password, salt)^25 */
-/* hashn = hash(nickname) */
-/* hashp = hash(!key_pasw, nickname) */
-/* priv = encrypt[!key_pasw](private_key) */
-CREATE TABLE IF NOT EXISTS users (
- id INTEGER,
- f2f BOOLEAN,
- hashn VARCHAR(255) UNIQUE,
- hashp VARCHAR(255),
- salt VARCHAR(255),
- priv TEXT,
- PRIMARY KEY(id)
-);
-/* hashn = hash(nickname, !key_pasw) */
-/* hashp = hash(public_key, !key_pasw) */
-/* name = encrypt[!key_pasw](nickname) */
-/* publ = encrypt[!key_pasw](public_key) */
-CREATE TABLE IF NOT EXISTS contacts (
- id INTEGER,
- id_user INTEGER,
- hashn VARCHAR(255) UNIQUE,
- hashp VARCHAR(255) UNIQUE,
- name NVARCHAR(255),
- publ TEXT,
- PRIMARY KEY(id),
- FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
-);
-/* hash = hash(host, !key_pasw) */
-/* host = encrypt[!key_pasw](host) */
-/* pasw = encrypt[!key_pasw](pasw) */
-CREATE TABLE IF NOT EXISTS connects (
- id INTEGER,
- id_user INTEGER,
- hash VARCHAR(255) UNIQUE,
- host VARCHAR(255),
- pasw VARCHAR(255),
- PRIMARY KEY(id),
- FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
-);
-/* hash = hash(pack_hash, !key_pasw) */
-/* spubl = encrypt[!key_pasw](public_key) */
-/* sname = encrypt[!key_pasw](nickname) */
-/* head = encrypt[!key_pasw](title) */
-/* body = encrypt[!key_pasw](message) */
-/* addtime = encrypt[!key_pasw](time_rec) */
-CREATE TABLE IF NOT EXISTS emails (
- id INTEGER,
- id_user INTEGER,
- deleted BOOLEAN DEFAULT 0,
- hash VARCHAR(255) UNIQUE,
- spubl TEXT,
- sname NVARCHAR(255),
- head NVARCHAR(255),
- body TEXT,
- addtime TEXT,
- PRIMARY KEY(id),
- FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
-);
-```
-
-### Email page
-
+# HES
+
+> Hidden email service. Version 1.1.10s.
+
+### Home page
+
+
+### Characteristics
+1. End to end encryption;
+2. Supported tor connections;
+3. Symmetric algorithm: AES-CBC;
+4. Asymmetric algorithm: RSA-OAEP, RSA-PSS;
+5. Hash function: SHA256;
+
+### Account page
+
+
+### Compile and run
+```
+$ make
+> go build client.go gconsts.go cdatabase.go cmodels.go csessions.go
+> go build server.go gconsts.go sdatabase.go sconfig.go
+$ ./server -open="localhost:8080" &
+$ ./client -open="localhost:7545"
+```
+
+### List of emails page
+
+
+### DB and CFG files
+> Database and config files are creates when the application starts.
+
+#### Server side db (server.db)
+```sql
+/* recv = hash(public_key) */
+/* hash = hash(data) */
+/* data = encrypt(email) */
+CREATE TABLE IF NOT EXISTS emails (
+ id INTEGER,
+ recv VARCHAR(255),
+ hash VARCHAR(255) UNIQUE,
+ data TEXT,
+ addtime DATETIME DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY(id)
+);
+```
+
+#### Server side cfg (server.cfg)
+```go
+type CFG struct {
+ Pasw string `json:"pasw"`
+ Conns [][2]string `json:"conns"`
+}
+```
+
+#### Client side db (client.db)
+```sql
+/* !key_pasw = hash(password, salt)^25 */
+/* hashn = hash(nickname) */
+/* hashp = hash(!key_pasw, nickname) */
+/* priv = encrypt[!key_pasw](private_key) */
+CREATE TABLE IF NOT EXISTS users (
+ id INTEGER,
+ f2f BOOLEAN,
+ hashn VARCHAR(255) UNIQUE,
+ hashp VARCHAR(255),
+ salt VARCHAR(255),
+ priv TEXT,
+ PRIMARY KEY(id)
+);
+/* hashn = hash(nickname, !key_pasw) */
+/* hashp = hash(public_key, !key_pasw) */
+/* name = encrypt[!key_pasw](nickname) */
+/* publ = encrypt[!key_pasw](public_key) */
+CREATE TABLE IF NOT EXISTS contacts (
+ id INTEGER,
+ id_user INTEGER,
+ hashn VARCHAR(255) UNIQUE,
+ hashp VARCHAR(255) UNIQUE,
+ name NVARCHAR(255),
+ publ TEXT,
+ PRIMARY KEY(id),
+ FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
+);
+/* hash = hash(host, !key_pasw) */
+/* host = encrypt[!key_pasw](host) */
+/* pasw = encrypt[!key_pasw](pasw) */
+CREATE TABLE IF NOT EXISTS connects (
+ id INTEGER,
+ id_user INTEGER,
+ hash VARCHAR(255) UNIQUE,
+ host VARCHAR(255),
+ pasw VARCHAR(255),
+ PRIMARY KEY(id),
+ FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
+);
+/* hash = hash(pack_hash, !key_pasw) */
+/* spubl = encrypt[!key_pasw](public_key) */
+/* sname = encrypt[!key_pasw](nickname) */
+/* head = encrypt[!key_pasw](title) */
+/* body = encrypt[!key_pasw](message) */
+/* addtime = encrypt[!key_pasw](time_rec) */
+CREATE TABLE IF NOT EXISTS emails (
+ id INTEGER,
+ id_user INTEGER,
+ deleted BOOLEAN DEFAULT 0,
+ hash VARCHAR(255) UNIQUE,
+ spubl TEXT,
+ sname NVARCHAR(255),
+ head NVARCHAR(255),
+ body TEXT,
+ addtime TEXT,
+ PRIMARY KEY(id),
+ FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
+);
+```
+
+### Email page
+
diff --git a/cdatabase.go b/cdatabase.go
index d5c00d4..470be46 100644
--- a/cdatabase.go
+++ b/cdatabase.go
@@ -1,511 +1,511 @@
-package main
-
-import (
- "bytes"
- "crypto/rsa"
- "database/sql"
- "encoding/json"
- "fmt"
- _ "github.com/mattn/go-sqlite3"
- gp "github.com/number571/gopeer"
- "strings"
- "time"
-)
-
-const (
- PASWDIFF = 25 // bits
- IS_EMAIL = "[IS-EMAIL]"
-)
-
-func NewDB(name string) *DB {
- db, err := sql.Open("sqlite3", name)
- if err != nil {
- return nil
- }
- _, err = db.Exec(`
-PRAGMA foreign_keys=ON;
-PRAGMA secure_delete=ON;
-CREATE TABLE IF NOT EXISTS users (
- id INTEGER,
- f2f BOOLEAN,
- hashn VARCHAR(255) UNIQUE,
- hashp VARCHAR(255),
- salt VARCHAR(255),
- priv TEXT,
- PRIMARY KEY(id)
-);
-CREATE TABLE IF NOT EXISTS contacts (
- id INTEGER,
- id_user INTEGER,
- hashn VARCHAR(255) UNIQUE,
- hashp VARCHAR(255) UNIQUE,
- name NVARCHAR(255),
- publ TEXT,
- PRIMARY KEY(id),
- FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
-);
-CREATE TABLE IF NOT EXISTS connects (
- id INTEGER,
- id_user INTEGER,
- hash VARCHAR(255) UNIQUE,
- host VARCHAR(255),
- pasw VARCHAR(255),
- PRIMARY KEY(id),
- FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
-);
-CREATE TABLE IF NOT EXISTS emails (
- id INTEGER,
- id_user INTEGER,
- deleted BOOLEAN DEFAULT 0,
- hash VARCHAR(255) UNIQUE,
- spubl TEXT,
- sname NVARCHAR(255),
- head NVARCHAR(255),
- body TEXT,
- addtime TEXT,
- PRIMARY KEY(id),
- FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
-);
-`)
- if err != nil {
- return nil
- }
- return &DB{
- ptr: db,
- }
-}
-
-func (db *DB) StateF2F(user *User) bool {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- var (
- f2f bool
- )
- row := db.ptr.QueryRow(
- "SELECT f2f FROM users WHERE hashn=$1",
- gp.Base64Encode(gp.HashSum([]byte(user.Name))),
- )
- row.Scan(&f2f)
- return f2f
-}
-
-func (db *DB) SwitchF2F(user *User) error {
- f2f := !db.StateF2F(user)
- db.mtx.Lock()
- defer db.mtx.Unlock()
- _, err := db.ptr.Exec(
- "UPDATE users SET f2f=$1 WHERE hashn=$2",
- f2f,
- gp.Base64Encode(gp.HashSum([]byte(user.Name))),
- )
- return err
-}
-
-func (db *DB) SetUser(name, pasw string, priv *rsa.PrivateKey) error {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- if priv == nil {
- return fmt.Errorf("private key is null")
- }
- name = strings.TrimSpace(name)
- if len(name) < 6 || len(name) > 64 {
- return fmt.Errorf("need len username >= 6 and <= 64")
- }
- if len(pasw) < 8 {
- return fmt.Errorf("need len password >= 8")
- }
- if db.userExist(name) {
- return fmt.Errorf("user already exist")
- }
- salt := gp.GenerateBytes(gp.Get("RAND_SIZE").(uint))
- bpasw := gp.RaiseEntropy([]byte(pasw), salt, PASWDIFF)
- hpasw := gp.HashSum(bytes.Join(
- [][]byte{
- bpasw,
- []byte(name),
- },
- []byte{},
- ))
- _, err := db.ptr.Exec(
- "INSERT INTO users (hashn, hashp, salt, priv, f2f) VALUES ($1, $2, $3, $4, 0)",
- gp.Base64Encode(gp.HashSum([]byte(name))),
- gp.Base64Encode(hpasw),
- gp.Base64Encode(salt),
- gp.Base64Encode(gp.EncryptAES(bpasw, gp.PrivateKeyToBytes(priv))),
- )
- return err
-}
-
-func (db *DB) GetUser(name, pasw string) *User {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- var (
- id int
- hpasw string
- ssalt string
- spriv string
- )
- name = strings.TrimSpace(name)
- row := db.ptr.QueryRow(
- "SELECT id, hashp, salt, priv FROM users WHERE hashn=$1",
- gp.Base64Encode(gp.HashSum([]byte(name))),
- )
- row.Scan(&id, &hpasw, &ssalt, &spriv)
- if spriv == "" {
- return nil
- }
- salt := gp.Base64Decode(ssalt)
- bpasw := gp.RaiseEntropy([]byte(pasw), salt, PASWDIFF)
- chpasw := gp.HashSum(bytes.Join(
- [][]byte{
- bpasw,
- []byte(name),
- },
- []byte{},
- ))
- if !bytes.Equal(chpasw, gp.Base64Decode(hpasw)) {
- return nil
- }
- priv := gp.BytesToPrivateKey(gp.DecryptAES(bpasw, gp.Base64Decode(spriv)))
- if priv == nil {
- return nil
- }
- return &User{
- Id: id,
- Name: name,
- Pasw: bpasw,
- Priv: priv,
- }
-}
-
-func (db *DB) DelUser(user *User) error {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- _, err := db.ptr.Exec(
- "DELETE FROM users WHERE id=$1",
- user.Id,
- )
- return err
-}
-
-func (db *DB) GetEmails(user *User, start, quan int) []Email {
- var (
- email *Email
- emails []Email
- )
- for i := start; i < start+quan; i++ {
- email = db.GetEmail(user, i)
- if email == nil {
- break
- }
- emails = append(emails, *email)
- }
- return emails
-}
-
-func (db *DB) GetEmail(user *User, id int) *Email {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- var (
- spubl string
- sname string
- head string
- body string
- hash string
- atime string
- )
- row := db.ptr.QueryRow(
- "SELECT spubl, sname, head, body, hash, addtime FROM emails WHERE id_user=$1 AND deleted=0 ORDER BY id DESC LIMIT 1 OFFSET $2",
- user.Id,
- id,
- )
- row.Scan(&spubl, &sname, &head, &body, &hash, &atime)
- if spubl == "" {
- return nil
- }
- return &Email{
- Id: id,
- Hash: hash,
- SenderPubl: gp.Base64Encode(gp.DecryptAES(user.Pasw, gp.Base64Decode(spubl))),
- SenderName: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(sname))),
- Head: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(head))),
- Body: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(body))),
- Time: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(atime))),
- }
-}
-
-func (db *DB) SetEmail(user *User, pack *gp.Package) error {
- pub := gp.BytesToPublicKey(pack.Head.Sender)
- if db.StateF2F(user) && !db.InContacts(user, pub) {
- return fmt.Errorf("sender not in contacts")
- }
- db.mtx.Lock()
- defer db.mtx.Unlock()
- if pack.Head.Title != IS_EMAIL {
- return fmt.Errorf("is not email")
- }
- if db.emailExist(user, gp.Base64Encode(pack.Body.Hash)) {
- return fmt.Errorf("email already exist")
- }
- var email Email
- err := json.Unmarshal([]byte(pack.Body.Data), &email)
- if err != nil {
- return fmt.Errorf("json decode")
- }
- name := strings.TrimSpace(email.SenderName)
- if len(name) < 6 || len(name) > 64 {
- return fmt.Errorf("len username < 6 or > 64")
- }
- head := strings.TrimSpace(email.Head)
- body := strings.TrimSpace(email.Body)
- if head == "" || body == "" {
- return fmt.Errorf("head or body is null")
- }
- heads := strings.Split(head, FSEPARAT)
- bodys := strings.Split(body, FSEPARAT)
- if len(heads) != len(bodys) {
- return fmt.Errorf("len.head != len.body")
- }
- _, err = db.ptr.Exec(
- "INSERT INTO emails (id_user, hash, spubl, sname, head, body, addtime) VALUES ($1, $2, $3, $4, $5, $6, $7)",
- user.Id,
- hashWithSecret(user, pack.Body.Hash),
- gp.Base64Encode(gp.EncryptAES(user.Pasw, pack.Head.Sender)),
- gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(name))),
- gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(head))),
- gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(body))),
- gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(time.Now().Format(time.RFC850)))),
- )
- return err
-}
-
-func (db *DB) DelEmail(user *User, hash string) error {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- _, err := db.ptr.Exec(
- "UPDATE emails SET deleted=1, spubl=NULL, sname=NULL, head=NULL, body=NULL, addtime=NULL WHERE id_user=$1 AND hash=$2",
- user.Id,
- hash,
- )
- return err
-}
-
-func (db *DB) GetContacts(user *User) map[string]string {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- var (
- name string
- spub string
- contacts = make(map[string]string)
- )
- rows, err := db.ptr.Query(
- "SELECT name, publ FROM contacts WHERE id_user=$1",
- user.Id,
- )
- if err != nil {
- return nil
- }
- defer rows.Close()
- for rows.Next() {
- err = rows.Scan(
- &name,
- &spub,
- )
- if err != nil {
- break
- }
- name = string(gp.DecryptAES(user.Pasw, gp.Base64Decode(name)))
- spub = gp.Base64Encode(gp.DecryptAES(user.Pasw, gp.Base64Decode(spub)))
- contacts[name] = spub
- }
- return contacts
-}
-
-func (db *DB) InContacts(user *User, pub *rsa.PublicKey) bool {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- var (
- namee string
- )
- row := db.ptr.QueryRow(
- "SELECT name FROM contacts WHERE id_user=$1 AND hashp=$2",
- user.Id,
- hashWithSecret(user, gp.PublicKeyToBytes(pub)),
- )
- row.Scan(&namee)
- return namee != ""
-}
-
-func (db *DB) SetContact(user *User, name string, pub *rsa.PublicKey) error {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- if pub == nil {
- return fmt.Errorf("public key is null")
- }
- name = strings.TrimSpace(name)
- if len(name) == 0 {
- return fmt.Errorf("nickname is null")
- }
- if db.contactExist(user, name, pub) {
- return fmt.Errorf("contact already exist")
- }
- spub := gp.PublicKeyToBytes(pub)
- _, err := db.ptr.Exec(
- "INSERT INTO contacts (id_user, hashn, hashp, name, publ) VALUES ($1, $2, $3, $4, $5)",
- user.Id,
- hashWithSecret(user, []byte(name)),
- hashWithSecret(user, spub),
- gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(name))),
- gp.Base64Encode(gp.EncryptAES(user.Pasw, spub)),
- )
- return err
-}
-
-func (db *DB) DelContact(user *User, pub *rsa.PublicKey) error {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- if pub == nil {
- return fmt.Errorf("public key is null")
- }
- _, err := db.ptr.Exec(
- "DELETE FROM contacts WHERE id_user=$1 AND hashp=$2",
- user.Id,
- hashWithSecret(user, gp.PublicKeyToBytes(pub)),
- )
- return err
-}
-
-func (db *DB) GetConns(user *User) [][2]string {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- var (
- conn string
- pasw string
- connects [][2]string
- )
- rows, err := db.ptr.Query(
- "SELECT host, pasw FROM connects WHERE id_user=$1",
- user.Id,
- )
- if err != nil {
- return nil
- }
- defer rows.Close()
- for rows.Next() {
- err = rows.Scan(
- &conn,
- &pasw,
- )
- if err != nil {
- break
- }
- connects = append(connects, [2]string{
- string(gp.DecryptAES(user.Pasw, gp.Base64Decode(conn))),
- string(gp.DecryptAES(user.Pasw, gp.Base64Decode(pasw))),
- })
- }
- return connects
-}
-
-func (db *DB) SetConn(user *User, host, pasw string) error {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- host = strings.TrimSpace(host)
- if len(host) == 0 {
- return fmt.Errorf("host is null")
- }
- if db.connExist(user, host) {
- _, err := db.ptr.Exec(
- "UPDATE connects SET pasw=$1 WHERE id_user=$2 AND hash=$3",
- gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(pasw))),
- user.Id,
- hashWithSecret(user, []byte(host)),
- )
- return err
- }
- _, err := db.ptr.Exec(
- "INSERT INTO connects (id_user, hash, host, pasw) VALUES ($1, $2, $3, $4)",
- user.Id,
- hashWithSecret(user, []byte(host)),
- gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(host))),
- gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(pasw))),
- )
- return err
-}
-
-func (db *DB) DelConn(user *User, host string) error {
- db.mtx.Lock()
- defer db.mtx.Unlock()
- host = strings.TrimSpace(host)
- if len(host) == 0 {
- return fmt.Errorf("host is null")
- }
- _, err := db.ptr.Exec(
- "DELETE FROM connects WHERE id_user=$1 AND hash=$2",
- user.Id,
- hashWithSecret(user, []byte(host)),
- )
- return err
-}
-
-func (db *DB) userExist(name string) bool {
- var (
- namee string
- )
- row := db.ptr.QueryRow(
- "SELECT name FROM users WHERE hashn=$1",
- gp.Base64Encode(gp.HashSum([]byte(name))),
- )
- row.Scan(&namee)
- return namee != ""
-}
-
-func (db *DB) contactExist(user *User, name string, pub *rsa.PublicKey) bool {
- var (
- namee string
- )
- row := db.ptr.QueryRow(
- "SELECT name FROM contacts WHERE id_user=$1 AND (hashn=$2 OR hashp=$3)",
- user.Id,
- hashWithSecret(user, []byte(name)),
- hashWithSecret(user, gp.PublicKeyToBytes(pub)),
- )
- row.Scan(&namee)
- return namee != ""
-}
-
-func (db *DB) connExist(user *User, host string) bool {
- var (
- hoste string
- )
- row := db.ptr.QueryRow(
- "SELECT host FROM connects WHERE id_user=$1 AND hash=$2",
- user.Id,
- hashWithSecret(user, []byte(host)),
- )
- row.Scan(&hoste)
- return hoste != ""
-}
-
-func (db *DB) emailExist(user *User, hash string) bool {
- var (
- hashe string
- )
- row := db.ptr.QueryRow(
- "SELECT hash FROM emails WHERE id_user=$1 AND hash=$2",
- user.Id,
- hashWithSecret(user, []byte(hash)),
- )
- row.Scan(&hashe)
- return hashe != ""
-}
-
-func hashWithSecret(user *User, data []byte) string {
- return gp.Base64Encode(gp.HashSum(bytes.Join(
- [][]byte{
- data,
- user.Pasw,
- },
- []byte{},
- )))
-}
+package main
+
+import (
+ "bytes"
+ "crypto/rsa"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ _ "github.com/mattn/go-sqlite3"
+ gp "github.com/number571/gopeer"
+ "strings"
+ "time"
+)
+
+const (
+ PASWDIFF = 25 // bits
+ IS_EMAIL = "[IS-EMAIL]"
+)
+
+func NewDB(name string) *DB {
+ db, err := sql.Open("sqlite3", name)
+ if err != nil {
+ return nil
+ }
+ _, err = db.Exec(`
+PRAGMA foreign_keys=ON;
+PRAGMA secure_delete=ON;
+CREATE TABLE IF NOT EXISTS users (
+ id INTEGER,
+ f2f BOOLEAN,
+ hashn VARCHAR(255) UNIQUE,
+ hashp VARCHAR(255),
+ salt VARCHAR(255),
+ priv TEXT,
+ PRIMARY KEY(id)
+);
+CREATE TABLE IF NOT EXISTS contacts (
+ id INTEGER,
+ id_user INTEGER,
+ hashn VARCHAR(255) UNIQUE,
+ hashp VARCHAR(255) UNIQUE,
+ name NVARCHAR(255),
+ publ TEXT,
+ PRIMARY KEY(id),
+ FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
+);
+CREATE TABLE IF NOT EXISTS connects (
+ id INTEGER,
+ id_user INTEGER,
+ hash VARCHAR(255) UNIQUE,
+ host VARCHAR(255),
+ pasw VARCHAR(255),
+ PRIMARY KEY(id),
+ FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
+);
+CREATE TABLE IF NOT EXISTS emails (
+ id INTEGER,
+ id_user INTEGER,
+ deleted BOOLEAN DEFAULT 0,
+ hash VARCHAR(255) UNIQUE,
+ spubl TEXT,
+ sname NVARCHAR(255),
+ head NVARCHAR(255),
+ body TEXT,
+ addtime TEXT,
+ PRIMARY KEY(id),
+ FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE
+);
+`)
+ if err != nil {
+ return nil
+ }
+ return &DB{
+ ptr: db,
+ }
+}
+
+func (db *DB) StateF2F(user *User) bool {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ var (
+ f2f bool
+ )
+ row := db.ptr.QueryRow(
+ "SELECT f2f FROM users WHERE hashn=$1",
+ gp.Base64Encode(gp.HashSum([]byte(user.Name))),
+ )
+ row.Scan(&f2f)
+ return f2f
+}
+
+func (db *DB) SwitchF2F(user *User) error {
+ f2f := !db.StateF2F(user)
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ _, err := db.ptr.Exec(
+ "UPDATE users SET f2f=$1 WHERE hashn=$2",
+ f2f,
+ gp.Base64Encode(gp.HashSum([]byte(user.Name))),
+ )
+ return err
+}
+
+func (db *DB) SetUser(name, pasw string, priv *rsa.PrivateKey) error {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ if priv == nil {
+ return fmt.Errorf("private key is null")
+ }
+ name = strings.TrimSpace(name)
+ if len(name) < 6 || len(name) > 64 {
+ return fmt.Errorf("need len username >= 6 and <= 64")
+ }
+ if len(pasw) < 8 {
+ return fmt.Errorf("need len password >= 8")
+ }
+ if db.userExist(name) {
+ return fmt.Errorf("user already exist")
+ }
+ salt := gp.GenerateBytes(gp.Get("RAND_SIZE").(uint))
+ bpasw := gp.RaiseEntropy([]byte(pasw), salt, PASWDIFF)
+ hpasw := gp.HashSum(bytes.Join(
+ [][]byte{
+ bpasw,
+ []byte(name),
+ },
+ []byte{},
+ ))
+ _, err := db.ptr.Exec(
+ "INSERT INTO users (hashn, hashp, salt, priv, f2f) VALUES ($1, $2, $3, $4, 0)",
+ gp.Base64Encode(gp.HashSum([]byte(name))),
+ gp.Base64Encode(hpasw),
+ gp.Base64Encode(salt),
+ gp.Base64Encode(gp.EncryptAES(bpasw, gp.PrivateKeyToBytes(priv))),
+ )
+ return err
+}
+
+func (db *DB) GetUser(name, pasw string) *User {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ var (
+ id int
+ hpasw string
+ ssalt string
+ spriv string
+ )
+ name = strings.TrimSpace(name)
+ row := db.ptr.QueryRow(
+ "SELECT id, hashp, salt, priv FROM users WHERE hashn=$1",
+ gp.Base64Encode(gp.HashSum([]byte(name))),
+ )
+ row.Scan(&id, &hpasw, &ssalt, &spriv)
+ if spriv == "" {
+ return nil
+ }
+ salt := gp.Base64Decode(ssalt)
+ bpasw := gp.RaiseEntropy([]byte(pasw), salt, PASWDIFF)
+ chpasw := gp.HashSum(bytes.Join(
+ [][]byte{
+ bpasw,
+ []byte(name),
+ },
+ []byte{},
+ ))
+ if !bytes.Equal(chpasw, gp.Base64Decode(hpasw)) {
+ return nil
+ }
+ priv := gp.BytesToPrivateKey(gp.DecryptAES(bpasw, gp.Base64Decode(spriv)))
+ if priv == nil {
+ return nil
+ }
+ return &User{
+ Id: id,
+ Name: name,
+ Pasw: bpasw,
+ Priv: priv,
+ }
+}
+
+func (db *DB) DelUser(user *User) error {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ _, err := db.ptr.Exec(
+ "DELETE FROM users WHERE id=$1",
+ user.Id,
+ )
+ return err
+}
+
+func (db *DB) GetEmails(user *User, start, quan int) []Email {
+ var (
+ email *Email
+ emails []Email
+ )
+ for i := start; i < start+quan; i++ {
+ email = db.GetEmail(user, i)
+ if email == nil {
+ break
+ }
+ emails = append(emails, *email)
+ }
+ return emails
+}
+
+func (db *DB) GetEmail(user *User, id int) *Email {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ var (
+ spubl string
+ sname string
+ head string
+ body string
+ hash string
+ atime string
+ )
+ row := db.ptr.QueryRow(
+ "SELECT spubl, sname, head, body, hash, addtime FROM emails WHERE id_user=$1 AND deleted=0 ORDER BY id DESC LIMIT 1 OFFSET $2",
+ user.Id,
+ id,
+ )
+ row.Scan(&spubl, &sname, &head, &body, &hash, &atime)
+ if spubl == "" {
+ return nil
+ }
+ return &Email{
+ Id: id,
+ Hash: hash,
+ SenderPubl: gp.Base64Encode(gp.DecryptAES(user.Pasw, gp.Base64Decode(spubl))),
+ SenderName: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(sname))),
+ Head: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(head))),
+ Body: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(body))),
+ Time: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(atime))),
+ }
+}
+
+func (db *DB) SetEmail(user *User, pack *gp.Package) error {
+ pub := gp.BytesToPublicKey(pack.Head.Sender)
+ if db.StateF2F(user) && !db.InContacts(user, pub) {
+ return fmt.Errorf("sender not in contacts")
+ }
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ if pack.Head.Title != IS_EMAIL {
+ return fmt.Errorf("is not email")
+ }
+ if db.emailExist(user, gp.Base64Encode(pack.Body.Hash)) {
+ return fmt.Errorf("email already exist")
+ }
+ var email Email
+ err := json.Unmarshal([]byte(pack.Body.Data), &email)
+ if err != nil {
+ return fmt.Errorf("json decode")
+ }
+ name := strings.TrimSpace(email.SenderName)
+ if len(name) < 6 || len(name) > 64 {
+ return fmt.Errorf("len username < 6 or > 64")
+ }
+ head := strings.TrimSpace(email.Head)
+ body := strings.TrimSpace(email.Body)
+ if head == "" || body == "" {
+ return fmt.Errorf("head or body is null")
+ }
+ heads := strings.Split(head, FSEPARAT)
+ bodys := strings.Split(body, FSEPARAT)
+ if len(heads) != len(bodys) {
+ return fmt.Errorf("len.head != len.body")
+ }
+ _, err = db.ptr.Exec(
+ "INSERT INTO emails (id_user, hash, spubl, sname, head, body, addtime) VALUES ($1, $2, $3, $4, $5, $6, $7)",
+ user.Id,
+ hashWithSecret(user, pack.Body.Hash),
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, pack.Head.Sender)),
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(name))),
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(head))),
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(body))),
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(time.Now().Format(time.RFC850)))),
+ )
+ return err
+}
+
+func (db *DB) DelEmail(user *User, hash string) error {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ _, err := db.ptr.Exec(
+ "UPDATE emails SET deleted=1, spubl=NULL, sname=NULL, head=NULL, body=NULL, addtime=NULL WHERE id_user=$1 AND hash=$2",
+ user.Id,
+ hash,
+ )
+ return err
+}
+
+func (db *DB) GetContacts(user *User) map[string]string {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ var (
+ name string
+ spub string
+ contacts = make(map[string]string)
+ )
+ rows, err := db.ptr.Query(
+ "SELECT name, publ FROM contacts WHERE id_user=$1",
+ user.Id,
+ )
+ if err != nil {
+ return nil
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err = rows.Scan(
+ &name,
+ &spub,
+ )
+ if err != nil {
+ break
+ }
+ name = string(gp.DecryptAES(user.Pasw, gp.Base64Decode(name)))
+ spub = gp.Base64Encode(gp.DecryptAES(user.Pasw, gp.Base64Decode(spub)))
+ contacts[name] = spub
+ }
+ return contacts
+}
+
+func (db *DB) InContacts(user *User, pub *rsa.PublicKey) bool {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ var (
+ namee string
+ )
+ row := db.ptr.QueryRow(
+ "SELECT name FROM contacts WHERE id_user=$1 AND hashp=$2",
+ user.Id,
+ hashWithSecret(user, gp.PublicKeyToBytes(pub)),
+ )
+ row.Scan(&namee)
+ return namee != ""
+}
+
+func (db *DB) SetContact(user *User, name string, pub *rsa.PublicKey) error {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ if pub == nil {
+ return fmt.Errorf("public key is null")
+ }
+ name = strings.TrimSpace(name)
+ if len(name) == 0 {
+ return fmt.Errorf("nickname is null")
+ }
+ if db.contactExist(user, name, pub) {
+ return fmt.Errorf("contact already exist")
+ }
+ spub := gp.PublicKeyToBytes(pub)
+ _, err := db.ptr.Exec(
+ "INSERT INTO contacts (id_user, hashn, hashp, name, publ) VALUES ($1, $2, $3, $4, $5)",
+ user.Id,
+ hashWithSecret(user, []byte(name)),
+ hashWithSecret(user, spub),
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(name))),
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, spub)),
+ )
+ return err
+}
+
+func (db *DB) DelContact(user *User, pub *rsa.PublicKey) error {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ if pub == nil {
+ return fmt.Errorf("public key is null")
+ }
+ _, err := db.ptr.Exec(
+ "DELETE FROM contacts WHERE id_user=$1 AND hashp=$2",
+ user.Id,
+ hashWithSecret(user, gp.PublicKeyToBytes(pub)),
+ )
+ return err
+}
+
+func (db *DB) GetConns(user *User) [][2]string {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ var (
+ conn string
+ pasw string
+ connects [][2]string
+ )
+ rows, err := db.ptr.Query(
+ "SELECT host, pasw FROM connects WHERE id_user=$1",
+ user.Id,
+ )
+ if err != nil {
+ return nil
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err = rows.Scan(
+ &conn,
+ &pasw,
+ )
+ if err != nil {
+ break
+ }
+ connects = append(connects, [2]string{
+ string(gp.DecryptAES(user.Pasw, gp.Base64Decode(conn))),
+ string(gp.DecryptAES(user.Pasw, gp.Base64Decode(pasw))),
+ })
+ }
+ return connects
+}
+
+func (db *DB) SetConn(user *User, host, pasw string) error {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ host = strings.TrimSpace(host)
+ if len(host) == 0 {
+ return fmt.Errorf("host is null")
+ }
+ if db.connExist(user, host) {
+ _, err := db.ptr.Exec(
+ "UPDATE connects SET pasw=$1 WHERE id_user=$2 AND hash=$3",
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(pasw))),
+ user.Id,
+ hashWithSecret(user, []byte(host)),
+ )
+ return err
+ }
+ _, err := db.ptr.Exec(
+ "INSERT INTO connects (id_user, hash, host, pasw) VALUES ($1, $2, $3, $4)",
+ user.Id,
+ hashWithSecret(user, []byte(host)),
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(host))),
+ gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(pasw))),
+ )
+ return err
+}
+
+func (db *DB) DelConn(user *User, host string) error {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+ host = strings.TrimSpace(host)
+ if len(host) == 0 {
+ return fmt.Errorf("host is null")
+ }
+ _, err := db.ptr.Exec(
+ "DELETE FROM connects WHERE id_user=$1 AND hash=$2",
+ user.Id,
+ hashWithSecret(user, []byte(host)),
+ )
+ return err
+}
+
+func (db *DB) userExist(name string) bool {
+ var (
+ namee string
+ )
+ row := db.ptr.QueryRow(
+ "SELECT name FROM users WHERE hashn=$1",
+ gp.Base64Encode(gp.HashSum([]byte(name))),
+ )
+ row.Scan(&namee)
+ return namee != ""
+}
+
+func (db *DB) contactExist(user *User, name string, pub *rsa.PublicKey) bool {
+ var (
+ namee string
+ )
+ row := db.ptr.QueryRow(
+ "SELECT name FROM contacts WHERE id_user=$1 AND (hashn=$2 OR hashp=$3)",
+ user.Id,
+ hashWithSecret(user, []byte(name)),
+ hashWithSecret(user, gp.PublicKeyToBytes(pub)),
+ )
+ row.Scan(&namee)
+ return namee != ""
+}
+
+func (db *DB) connExist(user *User, host string) bool {
+ var (
+ hoste string
+ )
+ row := db.ptr.QueryRow(
+ "SELECT host FROM connects WHERE id_user=$1 AND hash=$2",
+ user.Id,
+ hashWithSecret(user, []byte(host)),
+ )
+ row.Scan(&hoste)
+ return hoste != ""
+}
+
+func (db *DB) emailExist(user *User, hash string) bool {
+ var (
+ hashe string
+ )
+ row := db.ptr.QueryRow(
+ "SELECT hash FROM emails WHERE id_user=$1 AND hash=$2",
+ user.Id,
+ hashWithSecret(user, []byte(hash)),
+ )
+ row.Scan(&hashe)
+ return hashe != ""
+}
+
+func hashWithSecret(user *User, data []byte) string {
+ return gp.Base64Encode(gp.HashSum(bytes.Join(
+ [][]byte{
+ data,
+ user.Pasw,
+ },
+ []byte{},
+ )))
+}
diff --git a/client.go b/client.go
index f9df6df..bd2b8e2 100644
--- a/client.go
+++ b/client.go
@@ -1,790 +1,790 @@
-package main
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "github.com/boombuler/barcode"
- "github.com/boombuler/barcode/qr"
- gp "github.com/number571/gopeer"
- "html/template"
- "image/png"
- "io/ioutil"
- "net/http"
- "os"
- "strconv"
- "strings"
- "time"
-)
-
-type TemplateResult struct {
- Auth string
- Result string
- Return int
-}
-
-const (
- FSEPARAT = "\001\007\005\000\005\007\001"
- MAXEPAGE = 5 // view emails in one page
- MAXCOUNT = 5 // load emails from one node
-)
-
-const (
- RET_SUCCESS = 0
- RET_DANGER = 1
- RET_WARNING = 2
-)
-
-const (
- PATH_VIEWS = "userside/views/"
- PATH_STATIC = "userside/static/"
-)
-
-var (
- DATABASE = NewDB("client.db")
- SESSIONS = NewSessions()
-)
-
-func init() {
- go delOldSessionsByTime(1*time.Hour, 15*time.Minute)
- hesDefaultInit("localhost:7545")
- fmt.Println("Client is listening...\n")
-}
-
-func delOldSessionsByTime(deltime, period time.Duration) {
- for {
- SESSIONS.DelByTime(deltime)
- time.Sleep(period)
- }
-}
-
-func main() {
- http.Handle("/static/", http.StripPrefix(
- "/static/",
- handleFileServer(http.Dir(PATH_STATIC))),
- )
- http.HandleFunc("/", indexPage)
- http.HandleFunc("/account", accountPage)
- http.HandleFunc("/account/public_key", accountPublicKeyPage)
- http.HandleFunc("/account/private_key", accountPrivateKeyPage)
- http.HandleFunc("/signup", signupPage)
- http.HandleFunc("/signin", signinPage)
- http.HandleFunc("/signout", signoutPage)
- http.HandleFunc("/network", networkPage)
- http.HandleFunc("/network/read", networkReadPage)
- http.HandleFunc("/network/write", networkWritePage)
- http.HandleFunc("/network/contact", networkContactPage)
- http.HandleFunc("/network/connect", networkConnectPage)
- http.ListenAndServe(OPENADDR, nil)
-}
-
-func handleFileServer(fs http.FileSystem) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if _, err := fs.Open(r.URL.Path); os.IsNotExist(err) {
- indexPage(w, r)
- return
- }
- http.FileServer(fs).ServeHTTP(w, r)
- })
-}
-
-func indexPage(w http.ResponseWriter, r *http.Request) {
- t, err := template.ParseFiles(
- PATH_VIEWS+"base.html",
- PATH_VIEWS+"index.html",
- )
- if err != nil {
- panic("error: load index.html")
- }
- t.Execute(w, TemplateResult{
- Auth: getName(SESSIONS.Get(r)),
- })
-}
-
-func signupPage(w http.ResponseWriter, r *http.Request) {
- retcod, result := makeResult(RET_SUCCESS, "")
- t, err := template.ParseFiles(
- PATH_VIEWS+"base.html",
- PATH_VIEWS+"signup.html",
- )
- if err != nil {
- panic("error: load signup.html")
- }
- if SESSIONS.Get(r) != nil {
- http.Redirect(w, r, "/", 302)
- return
- }
- if r.Method == "POST" {
- name := r.FormValue("username")
- pasw := r.FormValue("password")
- spriv := r.FormValue("private_key")
- priv := gp.StringToPrivateKey(spriv)
- if pasw != r.FormValue("password_repeat") {
- retcod, result = makeResult(RET_DANGER, "error: passwords not equal")
- goto close
- }
- if spriv != "" && priv == nil {
- retcod, result = makeResult(RET_DANGER, "error: private key is not valid")
- goto close
- }
- if priv == nil {
- priv = gp.GenerateKey(gp.Get("AKEY_SIZE").(uint))
- }
- err := DATABASE.SetUser(name, pasw, priv)
- if err != nil {
- retcod, result = makeResult(RET_DANGER,
- fmt.Sprintf("error: %s", err.Error()))
- goto close
- }
- http.Redirect(w, r, "/signin", 302)
- return
- }
-close:
- t.Execute(w, TemplateResult{
- Result: result,
- Return: retcod,
- })
-}
-
-func signinPage(w http.ResponseWriter, r *http.Request) {
- retcod, result := makeResult(RET_SUCCESS, "")
- t, err := template.ParseFiles(
- PATH_VIEWS+"base.html",
- PATH_VIEWS+"signin.html",
- )
- if err != nil {
- panic("error: load signin.html")
- }
- if SESSIONS.Get(r) != nil {
- http.Redirect(w, r, "/", 302)
- return
- }
- if r.Method == "POST" {
- name := r.FormValue("username")
- pasw := r.FormValue("password")
- user := DATABASE.GetUser(name, pasw)
- if user == nil {
- retcod, result = makeResult(RET_DANGER, "error: username of password incorrect")
- goto close
- }
- SESSIONS.Set(w, user)
- http.Redirect(w, r, "/", 302)
- return
- }
-close:
- t.Execute(w, TemplateResult{
- Result: result,
- Return: retcod,
- })
-}
-
-func signoutPage(w http.ResponseWriter, r *http.Request) {
- SESSIONS.Del(w, r)
- http.Redirect(w, r, "/", 302)
-}
-
-func accountPage(w http.ResponseWriter, r *http.Request) {
- type AccountTemplateResult struct {
- TemplateResult
- PublicKey string
- PrivateKey string
- }
- retcod, result := makeResult(RET_SUCCESS, "")
- t, err := template.ParseFiles(
- PATH_VIEWS+"base.html",
- PATH_VIEWS+"account.html",
- )
- if err != nil {
- panic("error: load account.html")
- }
- user := SESSIONS.Get(r)
- if user == nil {
- http.Redirect(w, r, "/", 302)
- return
- }
- if r.Method == "POST" && r.FormValue("delete") != "" {
- name := r.FormValue("username")
- pasw := r.FormValue("password")
- cuser := DATABASE.GetUser(name, pasw)
- if cuser == nil || cuser.Id != user.Id {
- retcod, result = makeResult(RET_DANGER, "error: username of password incorrect")
- goto close
- }
- SESSIONS.Del(w, r)
- DATABASE.DelUser(cuser)
- http.Redirect(w, r, "/", 302)
- return
- }
-close:
- t.Execute(w, AccountTemplateResult{
- TemplateResult: TemplateResult{
- Auth: getName(SESSIONS.Get(r)),
- Result: result,
- Return: retcod,
- },
- PublicKey: gp.PublicKeyToString(&user.Priv.PublicKey),
- PrivateKey: gp.PrivateKeyToString(user.Priv),
- })
-}
-
-func accountPublicKeyPage(w http.ResponseWriter, r *http.Request) {
- user := SESSIONS.Get(r)
- if user == nil {
- fmt.Fprint(w, "error: session is null")
- return
- }
- dataString := gp.PublicKeyToString(&user.Priv.PublicKey)
- qrCode, err := qr.Encode(dataString, qr.Q, qr.Auto)
- if err != nil {
- fmt.Fprint(w, "error: qrcode generate")
- return
- }
- qrCode, err = barcode.Scale(qrCode, 768, 768)
- if err != nil {
- fmt.Fprint(w, "error: qrcode scale")
- return
- }
- png.Encode(w, qrCode)
-}
-
-func accountPrivateKeyPage(w http.ResponseWriter, r *http.Request) {
- user := SESSIONS.Get(r)
- if user == nil {
- fmt.Fprint(w, "error: session is null")
- return
- }
- dataString := gp.PrivateKeyToString(user.Priv)
- qrCode, err := qr.Encode(dataString, qr.Q, qr.Auto)
- if err != nil {
- fmt.Fprint(w, "error: qrcode generate")
- return
- }
- qrCode, err = barcode.Scale(qrCode, 768, 768)
- if err != nil {
- fmt.Fprint(w, "error: qrcode scale")
- return
- }
- png.Encode(w, qrCode)
-}
-
-func networkPage(w http.ResponseWriter, r *http.Request) {
- type ReadTemplateResult struct {
- TemplateResult
- Page int
- Emails []Email
- }
- page := 0
- retcod, result := makeResult(RET_SUCCESS, "")
- t, err := template.New("base.html").Funcs(template.FuncMap{
- "inc": func(x int) int { return x + 1 },
- "dec": func(x int) int { return x - 1 },
- "texts": getTexts,
- }).ParseFiles(
- PATH_VIEWS+"base.html",
- PATH_VIEWS+"network.html",
- )
- if err != nil {
- panic("error: load network.html")
- }
- t = template.Must(t, err)
- user := SESSIONS.Get(r)
- if user == nil {
- http.Redirect(w, r, "/", 302)
- return
- }
- if r.Method == "GET" && r.FormValue("page") != "" {
- num, err := strconv.Atoi(r.FormValue("num"))
- if err != nil {
- retcod, result = makeResult(RET_DANGER, "error: parse atoi")
- goto close
- }
- page = num
- }
- if r.Method == "POST" && r.FormValue("delete") != "" {
- hash := r.FormValue("email")
- DATABASE.DelEmail(user, hash)
- }
- if r.Method == "POST" && r.FormValue("update") != "" {
- conns := DATABASE.GetConns(user)
- for _, conn := range conns {
- go readEmails(user, conn[0])
- }
- time.Sleep(3 * time.Second)
- }
-close:
- t.Execute(w, ReadTemplateResult{
- TemplateResult: TemplateResult{
- Auth: getName(SESSIONS.Get(r)),
- Result: result,
- Return: retcod,
- },
- Page: page,
- Emails: DATABASE.GetEmails(user, page*MAXEPAGE, MAXEPAGE),
- })
-}
-
-// FS = FSEPARAT
-// head = title || FS || filename[0] || ... || FS || filename[n]
-// body = message || FS || base64(file[0]) || ... || FS || base64(file[n])
-func networkWritePage(w http.ResponseWriter, r *http.Request) {
- type WriteTemplateResult struct {
- TemplateResult
- Contacts map[string]string
- }
- type Resp struct {
- Result string `json:"result"`
- Return int `json:"return"`
- }
- type Req struct {
- Recv string `json:"recv"`
- Data string `json:"data"`
- Macp string `json:"macp"`
- }
- retcod, result := makeResult(RET_SUCCESS, "")
- t, err := template.ParseFiles(
- PATH_VIEWS+"base.html",
- PATH_VIEWS+"write.html",
- )
- if err != nil {
- panic("error: load write.html")
- }
- user := SESSIONS.Get(r)
- if user == nil {
- http.Redirect(w, r, "/", 302)
- return
- }
- if r.Method == "POST" {
- err := r.ParseMultipartForm(int64(MAXESIZE))
- if err != nil {
- retcod, result = makeResult(RET_DANGER, "error: max size")
- goto close
- }
- recv := gp.StringToPublicKey(r.FormValue("receiver"))
- if recv == nil {
- retcod, result = makeResult(RET_DANGER, "error: receiver is null")
- goto close
- }
- head := strings.TrimSpace(r.FormValue("title"))
- body := strings.TrimSpace(r.FormValue("message"))
- if head == "" || body == "" {
- retcod, result = makeResult(RET_DANGER, "error: head or body is null")
- goto close
- }
- files := r.MultipartForm.File["files"]
- for i := range files {
- file, err := files[i].Open()
- if err != nil {
- retcod, result = makeResult(RET_DANGER, "error: open file")
- goto close
- }
- content, err := ioutil.ReadAll(file)
- if err != nil {
- retcod, result = makeResult(RET_DANGER, "error: read file")
- goto close
- }
- file.Close()
- head += FSEPARAT + files[i].Filename
- body += FSEPARAT + gp.Base64Encode(content)
- }
- client := gp.NewClient(user.Priv, nil)
- pack := client.Encrypt(
- recv, newEmail(user.Name, head, body), POWSDIFF)
- hash := pack.Body.Hash
- conns := DATABASE.GetConns(user)
- req := Req{
- Recv: gp.HashPublicKey(recv),
- Data: string(gp.SerializePackage(pack)),
- }
- if uint(len(req.Data)) > MAXESIZE {
- retcod, result = makeResult(RET_DANGER, "error: max size")
- goto close
- }
- for _, conn := range conns {
- pasw := gp.HashSum([]byte(conn[1]))
- req.Macp = gp.Base64Encode(gp.EncryptAES(pasw, hash))
- go writeEmails(conn[0], serialize(req))
- }
- result = "success: email send"
- }
-close:
- t.Execute(w, WriteTemplateResult{
- TemplateResult: TemplateResult{
- Auth: getName(SESSIONS.Get(r)),
- Result: result,
- Return: retcod,
- },
- Contacts: DATABASE.GetContacts(user),
- })
-}
-
-func networkReadPage(w http.ResponseWriter, r *http.Request) {
- type ReadTemplateResult struct {
- TemplateResult
- Email *Email
- }
- retcod, result := makeResult(RET_SUCCESS, "")
- t, err := template.New("base.html").Funcs(template.FuncMap{
- "split": strings.Split,
- "texts": getTexts,
- "files": getFiles,
- }).ParseFiles(
- PATH_VIEWS+"base.html",
- PATH_VIEWS+"read.html",
- )
- if err != nil {
- panic("error: load read.html")
- }
- t = template.Must(t, err)
- user := SESSIONS.Get(r)
- if user == nil {
- http.Redirect(w, r, "/", 302)
- return
- }
- if r.Method == "POST" {
- pub := gp.StringToPublicKey(r.FormValue("public_key"))
- if pub == nil {
- fmt.Fprint(w, "error: public key is null")
- return
- }
- dataString := gp.PublicKeyToString(pub)
- qrCode, err := qr.Encode(dataString, qr.Q, qr.Auto)
- if err != nil {
- fmt.Fprint(w, "error: qrcode generate")
- return
- }
- qrCode, err = barcode.Scale(qrCode, 768, 768)
- if err != nil {
- fmt.Fprint(w, "error: qrcode scale")
- return
- }
- png.Encode(w, qrCode)
- return
- }
- var email *Email
- id, err := strconv.Atoi(r.FormValue("email"))
- if err != nil {
- retcod, result = makeResult(RET_DANGER, "error: atoi parse")
- goto close
- }
- email = DATABASE.GetEmail(user, id)
- if email == nil {
- retcod, result = makeResult(RET_DANGER, "error: email undefined")
- goto close
- }
-close:
- t.Execute(w, ReadTemplateResult{
- TemplateResult: TemplateResult{
- Auth: getName(SESSIONS.Get(r)),
- Result: result,
- Return: retcod,
- },
- Email: email,
- })
-}
-
-func networkContactPage(w http.ResponseWriter, r *http.Request) {
- type ContactTemplateResult struct {
- TemplateResult
- F2F bool
- Contacts map[string]string
- }
- retcod, result := makeResult(RET_SUCCESS, "")
- t, err := template.ParseFiles(
- PATH_VIEWS+"base.html",
- PATH_VIEWS+"contact.html",
- )
- if err != nil {
- panic("error: load contact.html")
- }
- user := SESSIONS.Get(r)
- if user == nil {
- http.Redirect(w, r, "/", 302)
- return
- }
- if r.Method == "POST" && r.FormValue("switchf2f") != "" {
- DATABASE.SwitchF2F(user)
- }
- if r.Method == "POST" && r.FormValue("append") != "" {
- name := r.FormValue("nickname")
- publ := gp.StringToPublicKey(r.FormValue("public_key"))
- err := DATABASE.SetContact(user, name, publ)
- if err != nil {
- retcod, result = makeResult(RET_DANGER,
- fmt.Sprintf("error: %s", err.Error()))
- goto close
- }
- }
- if r.Method == "POST" && r.FormValue("delete") != "" {
- publ := gp.StringToPublicKey(r.FormValue("public_key"))
- err := DATABASE.DelContact(user, publ)
- if err != nil {
- retcod, result = makeResult(RET_DANGER,
- fmt.Sprintf("error: %s", err.Error()))
- goto close
- }
- }
-close:
- t.Execute(w, ContactTemplateResult{
- TemplateResult: TemplateResult{
- Auth: getName(SESSIONS.Get(r)),
- Result: result,
- Return: retcod,
- },
- F2F: DATABASE.StateF2F(user),
- Contacts: DATABASE.GetContacts(user),
- })
-}
-
-func networkConnectPage(w http.ResponseWriter, r *http.Request) {
- type ConnTemplateResult struct {
- TemplateResult
- Connects [][2]string
- }
- retcod, result := makeResult(RET_SUCCESS, "")
- t, err := template.ParseFiles(
- PATH_VIEWS+"base.html",
- PATH_VIEWS+"connect.html",
- )
- if err != nil {
- panic("error: load connect.html")
- }
- user := SESSIONS.Get(r)
- if user == nil {
- http.Redirect(w, r, "/", 302)
- return
- }
- if r.Method == "POST" && r.FormValue("check") != "" {
- conns := DATABASE.GetConns(user)
- for _, conn := range conns {
- ret, res := checkConnection(conn)
- if ret != RET_SUCCESS {
- result += res
- retcod = RET_WARNING
- }
- }
- if retcod != RET_SUCCESS {
- goto close
- }
- result = "success: all connections work"
- }
- if r.Method == "POST" && r.FormValue("append") != "" {
- host := r.FormValue("hostname")
- pasw := r.FormValue("password")
- err := DATABASE.SetConn(user, host, pasw)
- if err != nil {
- retcod, result = makeResult(RET_DANGER,
- fmt.Sprintf("error: %s", err.Error()))
- goto close
- }
- }
- if r.Method == "POST" && r.FormValue("delete") != "" {
- host := r.FormValue("hostname")
- err := DATABASE.DelConn(user, host)
- if err != nil {
- retcod, result = makeResult(RET_DANGER,
- fmt.Sprintf("error: %s", err.Error()))
- goto close
- }
- }
-close:
- t.Execute(w, ConnTemplateResult{
- TemplateResult: TemplateResult{
- Auth: getName(SESSIONS.Get(r)),
- Result: result,
- Return: retcod,
- },
- Connects: DATABASE.GetConns(user),
- })
-}
-
-func checkConnection(conn [2]string) (int, string) {
- type Resp struct {
- Result string `json:"result"`
- Return int `json:"return"`
- }
- type Req struct {
- Macp string `json:"macp"`
- }
- var servresp Resp
- pasw := gp.HashSum([]byte(conn[1]))
- macp := gp.EncryptAES(pasw, []byte(TMESSAGE))
- resp, err := HTCLIENT.Post(
- "http://"+conn[0]+"/",
- "application/json",
- bytes.NewReader(serialize(Req{
- Macp: gp.Base64Encode(macp),
- })),
- )
- if err != nil {
- return makeResult(RET_DANGER,
- fmt.Sprintf("%s='%s';\n", conn[0], "error: connect"))
- }
- if resp.ContentLength > int64(MAXESIZE) {
- return makeResult(RET_DANGER,
- fmt.Sprintf("%s='%s';\n", conn[0], "error: max size"))
- }
- err = json.NewDecoder(resp.Body).Decode(&servresp)
- resp.Body.Close()
- if err != nil {
- return makeResult(RET_DANGER,
- fmt.Sprintf("%s='%s';\n", conn[0], "error: parse json"))
- }
- if servresp.Return != 0 {
- return makeResult(RET_DANGER,
- fmt.Sprintf("%s='%s';\n", conn[0], servresp.Result))
- }
- return makeResult(RET_SUCCESS, "")
-}
-
-func writeEmails(addr string, rdata []byte) {
- type Resp struct {
- Result string `json:"result"`
- Return int `json:"return"`
- }
- type Req struct {
- Recv string `json:"recv"`
- Data int `json:"data"`
- }
- var servresp Resp
- resp, err := HTCLIENT.Post(
- "http://"+addr+"/email/send",
- "application/json",
- bytes.NewReader(rdata),
- )
- if err != nil {
- return
- }
- if resp.ContentLength > int64(MAXESIZE) {
- return
- }
- err = json.NewDecoder(resp.Body).Decode(&servresp)
- resp.Body.Close()
- if err != nil {
- return
- }
- if servresp.Return != 0 {
- return
- }
-}
-
-func readEmails(user *User, addr string) {
- type Resp struct {
- Result string `json:"result"`
- Return int `json:"return"`
- }
- type Req struct {
- Recv string `json:"recv"`
- Data int `json:"data"`
- }
- var servresp Resp
- client := gp.NewClient(user.Priv, nil)
- pbhash := gp.HashPublicKey(client.PublicKey())
- // GET SIZE EMAILS
- resp, err := HTCLIENT.Post(
- "http://"+addr+"/email/recv",
- "application/json",
- bytes.NewReader(serialize(Req{
- Recv: pbhash,
- Data: 0,
- })),
- )
- if err != nil {
- return
- }
- if resp.ContentLength > int64(MAXESIZE) {
- return
- }
- err = json.NewDecoder(resp.Body).Decode(&servresp)
- resp.Body.Close()
- if err != nil {
- return
- }
- if servresp.Return != 0 {
- return
- }
- // GET DATA EMAILS
- size, err := strconv.Atoi(servresp.Result)
- if err != nil {
- return
- }
- for i, count := 1, 0; i <= size; i++ {
- resp, err := HTCLIENT.Post(
- "http://"+addr+"/email/recv",
- "application/json",
- bytes.NewReader(serialize(Req{
- Recv: pbhash,
- Data: i,
- })),
- )
- if err != nil {
- break
- }
- if resp.ContentLength > int64(MAXESIZE) {
- break
- }
- err = json.NewDecoder(resp.Body).Decode(&servresp)
- resp.Body.Close()
- if err != nil {
- break
- }
- if servresp.Return != 0 {
- continue
- }
- pack := gp.DeserializePackage([]byte(servresp.Result))
- if pack == nil {
- continue
- }
- pack = client.Decrypt(pack, POWSDIFF)
- if pack == nil {
- continue
- }
- err = DATABASE.SetEmail(user, pack)
- if err == nil {
- count++
- }
- if count == MAXCOUNT {
- break
- }
- }
-}
-
-func getTexts(email *Email) [2]string {
- head := strings.Split(email.Head, FSEPARAT)[0]
- body := strings.Split(email.Body, FSEPARAT)[0]
- return [2]string{
- head,
- body,
- }
-}
-
-func getFiles(email *Email) [][2]string {
- var list [][2]string
- name := strings.Split(email.Head, FSEPARAT)[1:]
- data := strings.Split(email.Body, FSEPARAT)[1:]
- for i := range name {
- list = append(list, [2]string{
- name[i],
- data[i],
- })
- }
- return list
-}
-
-func newEmail(sender, head, body string) *gp.Package {
- return gp.NewPackage(IS_EMAIL, serialize(Email{
- SenderName: sender,
- Head: head,
- Body: body,
- }))
-}
-
-func getName(user *User) string {
- if user == nil {
- return ""
- }
- return user.Name
-}
-
-func makeResult(retcod int, result string) (int, string) {
- return retcod, result
-}
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "github.com/boombuler/barcode"
+ "github.com/boombuler/barcode/qr"
+ gp "github.com/number571/gopeer"
+ "html/template"
+ "image/png"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type TemplateResult struct {
+ Auth string
+ Result string
+ Return int
+}
+
+const (
+ FSEPARAT = "\001\007\005\000\005\007\001"
+ MAXEPAGE = 5 // view emails in one page
+ MAXCOUNT = 5 // load emails from one node
+)
+
+const (
+ RET_SUCCESS = 0
+ RET_DANGER = 1
+ RET_WARNING = 2
+)
+
+const (
+ PATH_VIEWS = "userside/views/"
+ PATH_STATIC = "userside/static/"
+)
+
+var (
+ DATABASE = NewDB("client.db")
+ SESSIONS = NewSessions()
+)
+
+func init() {
+ go delOldSessionsByTime(1*time.Hour, 15*time.Minute)
+ hesDefaultInit("localhost:7545")
+ fmt.Printf("Client is listening [%s] ...\n\n", OPENADDR)
+}
+
+func delOldSessionsByTime(deltime, period time.Duration) {
+ for {
+ SESSIONS.DelByTime(deltime)
+ time.Sleep(period)
+ }
+}
+
+func main() {
+ http.Handle("/static/", http.StripPrefix(
+ "/static/",
+ handleFileServer(http.Dir(PATH_STATIC))),
+ )
+ http.HandleFunc("/", indexPage)
+ http.HandleFunc("/account", accountPage)
+ http.HandleFunc("/account/public_key", accountPublicKeyPage)
+ http.HandleFunc("/account/private_key", accountPrivateKeyPage)
+ http.HandleFunc("/signup", signupPage)
+ http.HandleFunc("/signin", signinPage)
+ http.HandleFunc("/signout", signoutPage)
+ http.HandleFunc("/network", networkPage)
+ http.HandleFunc("/network/read", networkReadPage)
+ http.HandleFunc("/network/write", networkWritePage)
+ http.HandleFunc("/network/contact", networkContactPage)
+ http.HandleFunc("/network/connect", networkConnectPage)
+ http.ListenAndServe(OPENADDR, nil)
+}
+
+func handleFileServer(fs http.FileSystem) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if _, err := fs.Open(r.URL.Path); os.IsNotExist(err) {
+ indexPage(w, r)
+ return
+ }
+ http.FileServer(fs).ServeHTTP(w, r)
+ })
+}
+
+func indexPage(w http.ResponseWriter, r *http.Request) {
+ t, err := template.ParseFiles(
+ PATH_VIEWS+"base.html",
+ PATH_VIEWS+"index.html",
+ )
+ if err != nil {
+ panic("error: load index.html")
+ }
+ t.Execute(w, TemplateResult{
+ Auth: getName(SESSIONS.Get(r)),
+ })
+}
+
+func signupPage(w http.ResponseWriter, r *http.Request) {
+ retcod, result := makeResult(RET_SUCCESS, "")
+ t, err := template.ParseFiles(
+ PATH_VIEWS+"base.html",
+ PATH_VIEWS+"signup.html",
+ )
+ if err != nil {
+ panic("error: load signup.html")
+ }
+ if SESSIONS.Get(r) != nil {
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+ if r.Method == "POST" {
+ name := r.FormValue("username")
+ pasw := r.FormValue("password")
+ spriv := r.FormValue("private_key")
+ priv := gp.StringToPrivateKey(spriv)
+ if pasw != r.FormValue("password_repeat") {
+ retcod, result = makeResult(RET_DANGER, "error: passwords not equal")
+ goto close
+ }
+ if spriv != "" && priv == nil {
+ retcod, result = makeResult(RET_DANGER, "error: private key is not valid")
+ goto close
+ }
+ if priv == nil {
+ priv = gp.GenerateKey(gp.Get("AKEY_SIZE").(uint))
+ }
+ err := DATABASE.SetUser(name, pasw, priv)
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER,
+ fmt.Sprintf("error: %s", err.Error()))
+ goto close
+ }
+ http.Redirect(w, r, "/signin", 302)
+ return
+ }
+close:
+ t.Execute(w, TemplateResult{
+ Result: result,
+ Return: retcod,
+ })
+}
+
+func signinPage(w http.ResponseWriter, r *http.Request) {
+ retcod, result := makeResult(RET_SUCCESS, "")
+ t, err := template.ParseFiles(
+ PATH_VIEWS+"base.html",
+ PATH_VIEWS+"signin.html",
+ )
+ if err != nil {
+ panic("error: load signin.html")
+ }
+ if SESSIONS.Get(r) != nil {
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+ if r.Method == "POST" {
+ name := r.FormValue("username")
+ pasw := r.FormValue("password")
+ user := DATABASE.GetUser(name, pasw)
+ if user == nil {
+ retcod, result = makeResult(RET_DANGER, "error: username of password incorrect")
+ goto close
+ }
+ SESSIONS.Set(w, user)
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+close:
+ t.Execute(w, TemplateResult{
+ Result: result,
+ Return: retcod,
+ })
+}
+
+func signoutPage(w http.ResponseWriter, r *http.Request) {
+ SESSIONS.Del(w, r)
+ http.Redirect(w, r, "/", 302)
+}
+
+func accountPage(w http.ResponseWriter, r *http.Request) {
+ type AccountTemplateResult struct {
+ TemplateResult
+ PublicKey string
+ PrivateKey string
+ }
+ retcod, result := makeResult(RET_SUCCESS, "")
+ t, err := template.ParseFiles(
+ PATH_VIEWS+"base.html",
+ PATH_VIEWS+"account.html",
+ )
+ if err != nil {
+ panic("error: load account.html")
+ }
+ user := SESSIONS.Get(r)
+ if user == nil {
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+ if r.Method == "POST" && r.FormValue("delete") != "" {
+ name := r.FormValue("username")
+ pasw := r.FormValue("password")
+ cuser := DATABASE.GetUser(name, pasw)
+ if cuser == nil || cuser.Id != user.Id {
+ retcod, result = makeResult(RET_DANGER, "error: username of password incorrect")
+ goto close
+ }
+ SESSIONS.Del(w, r)
+ DATABASE.DelUser(cuser)
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+close:
+ t.Execute(w, AccountTemplateResult{
+ TemplateResult: TemplateResult{
+ Auth: getName(SESSIONS.Get(r)),
+ Result: result,
+ Return: retcod,
+ },
+ PublicKey: gp.PublicKeyToString(&user.Priv.PublicKey),
+ PrivateKey: gp.PrivateKeyToString(user.Priv),
+ })
+}
+
+func accountPublicKeyPage(w http.ResponseWriter, r *http.Request) {
+ user := SESSIONS.Get(r)
+ if user == nil {
+ fmt.Fprint(w, "error: session is null")
+ return
+ }
+ dataString := gp.PublicKeyToString(&user.Priv.PublicKey)
+ qrCode, err := qr.Encode(dataString, qr.Q, qr.Auto)
+ if err != nil {
+ fmt.Fprint(w, "error: qrcode generate")
+ return
+ }
+ qrCode, err = barcode.Scale(qrCode, 768, 768)
+ if err != nil {
+ fmt.Fprint(w, "error: qrcode scale")
+ return
+ }
+ png.Encode(w, qrCode)
+}
+
+func accountPrivateKeyPage(w http.ResponseWriter, r *http.Request) {
+ user := SESSIONS.Get(r)
+ if user == nil {
+ fmt.Fprint(w, "error: session is null")
+ return
+ }
+ dataString := gp.PrivateKeyToString(user.Priv)
+ qrCode, err := qr.Encode(dataString, qr.Q, qr.Auto)
+ if err != nil {
+ fmt.Fprint(w, "error: qrcode generate")
+ return
+ }
+ qrCode, err = barcode.Scale(qrCode, 768, 768)
+ if err != nil {
+ fmt.Fprint(w, "error: qrcode scale")
+ return
+ }
+ png.Encode(w, qrCode)
+}
+
+func networkPage(w http.ResponseWriter, r *http.Request) {
+ type ReadTemplateResult struct {
+ TemplateResult
+ Page int
+ Emails []Email
+ }
+ page := 0
+ retcod, result := makeResult(RET_SUCCESS, "")
+ t, err := template.New("base.html").Funcs(template.FuncMap{
+ "inc": func(x int) int { return x + 1 },
+ "dec": func(x int) int { return x - 1 },
+ "texts": getTexts,
+ }).ParseFiles(
+ PATH_VIEWS+"base.html",
+ PATH_VIEWS+"network.html",
+ )
+ if err != nil {
+ panic("error: load network.html")
+ }
+ t = template.Must(t, err)
+ user := SESSIONS.Get(r)
+ if user == nil {
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+ if r.Method == "GET" && r.FormValue("page") != "" {
+ num, err := strconv.Atoi(r.FormValue("num"))
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER, "error: parse atoi")
+ goto close
+ }
+ page = num
+ }
+ if r.Method == "POST" && r.FormValue("delete") != "" {
+ hash := r.FormValue("email")
+ DATABASE.DelEmail(user, hash)
+ }
+ if r.Method == "POST" && r.FormValue("update") != "" {
+ conns := DATABASE.GetConns(user)
+ for _, conn := range conns {
+ go readEmails(user, conn[0])
+ }
+ time.Sleep(3 * time.Second)
+ }
+close:
+ t.Execute(w, ReadTemplateResult{
+ TemplateResult: TemplateResult{
+ Auth: getName(SESSIONS.Get(r)),
+ Result: result,
+ Return: retcod,
+ },
+ Page: page,
+ Emails: DATABASE.GetEmails(user, page*MAXEPAGE, MAXEPAGE),
+ })
+}
+
+// FS = FSEPARAT
+// head = title || FS || filename[0] || ... || FS || filename[n]
+// body = message || FS || base64(file[0]) || ... || FS || base64(file[n])
+func networkWritePage(w http.ResponseWriter, r *http.Request) {
+ type WriteTemplateResult struct {
+ TemplateResult
+ Contacts map[string]string
+ }
+ type Resp struct {
+ Result string `json:"result"`
+ Return int `json:"return"`
+ }
+ type Req struct {
+ Recv string `json:"recv"`
+ Data string `json:"data"`
+ Macp string `json:"macp"`
+ }
+ retcod, result := makeResult(RET_SUCCESS, "")
+ t, err := template.ParseFiles(
+ PATH_VIEWS+"base.html",
+ PATH_VIEWS+"write.html",
+ )
+ if err != nil {
+ panic("error: load write.html")
+ }
+ user := SESSIONS.Get(r)
+ if user == nil {
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+ if r.Method == "POST" {
+ err := r.ParseMultipartForm(int64(MAXESIZE))
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER, "error: max size")
+ goto close
+ }
+ recv := gp.StringToPublicKey(r.FormValue("receiver"))
+ if recv == nil {
+ retcod, result = makeResult(RET_DANGER, "error: receiver is null")
+ goto close
+ }
+ head := strings.TrimSpace(r.FormValue("title"))
+ body := strings.TrimSpace(r.FormValue("message"))
+ if head == "" || body == "" {
+ retcod, result = makeResult(RET_DANGER, "error: head or body is null")
+ goto close
+ }
+ files := r.MultipartForm.File["files"]
+ for i := range files {
+ file, err := files[i].Open()
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER, "error: open file")
+ goto close
+ }
+ content, err := ioutil.ReadAll(file)
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER, "error: read file")
+ goto close
+ }
+ file.Close()
+ head += FSEPARAT + files[i].Filename
+ body += FSEPARAT + gp.Base64Encode(content)
+ }
+ client := gp.NewClient(user.Priv, nil)
+ pack := client.Encrypt(
+ recv, newEmail(user.Name, head, body), POWSDIFF)
+ hash := pack.Body.Hash
+ conns := DATABASE.GetConns(user)
+ req := Req{
+ Recv: gp.HashPublicKey(recv),
+ Data: string(gp.SerializePackage(pack)),
+ }
+ if uint(len(req.Data)) > MAXESIZE {
+ retcod, result = makeResult(RET_DANGER, "error: max size")
+ goto close
+ }
+ for _, conn := range conns {
+ pasw := gp.HashSum([]byte(conn[1]))
+ req.Macp = gp.Base64Encode(gp.EncryptAES(pasw, hash))
+ go writeEmails(conn[0], serialize(req))
+ }
+ result = "success: email send"
+ }
+close:
+ t.Execute(w, WriteTemplateResult{
+ TemplateResult: TemplateResult{
+ Auth: getName(SESSIONS.Get(r)),
+ Result: result,
+ Return: retcod,
+ },
+ Contacts: DATABASE.GetContacts(user),
+ })
+}
+
+func networkReadPage(w http.ResponseWriter, r *http.Request) {
+ type ReadTemplateResult struct {
+ TemplateResult
+ Email *Email
+ }
+ retcod, result := makeResult(RET_SUCCESS, "")
+ t, err := template.New("base.html").Funcs(template.FuncMap{
+ "split": strings.Split,
+ "texts": getTexts,
+ "files": getFiles,
+ }).ParseFiles(
+ PATH_VIEWS+"base.html",
+ PATH_VIEWS+"read.html",
+ )
+ if err != nil {
+ panic("error: load read.html")
+ }
+ t = template.Must(t, err)
+ user := SESSIONS.Get(r)
+ if user == nil {
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+ if r.Method == "POST" {
+ pub := gp.StringToPublicKey(r.FormValue("public_key"))
+ if pub == nil {
+ fmt.Fprint(w, "error: public key is null")
+ return
+ }
+ dataString := gp.PublicKeyToString(pub)
+ qrCode, err := qr.Encode(dataString, qr.Q, qr.Auto)
+ if err != nil {
+ fmt.Fprint(w, "error: qrcode generate")
+ return
+ }
+ qrCode, err = barcode.Scale(qrCode, 768, 768)
+ if err != nil {
+ fmt.Fprint(w, "error: qrcode scale")
+ return
+ }
+ png.Encode(w, qrCode)
+ return
+ }
+ var email *Email
+ id, err := strconv.Atoi(r.FormValue("email"))
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER, "error: atoi parse")
+ goto close
+ }
+ email = DATABASE.GetEmail(user, id)
+ if email == nil {
+ retcod, result = makeResult(RET_DANGER, "error: email undefined")
+ goto close
+ }
+close:
+ t.Execute(w, ReadTemplateResult{
+ TemplateResult: TemplateResult{
+ Auth: getName(SESSIONS.Get(r)),
+ Result: result,
+ Return: retcod,
+ },
+ Email: email,
+ })
+}
+
+func networkContactPage(w http.ResponseWriter, r *http.Request) {
+ type ContactTemplateResult struct {
+ TemplateResult
+ F2F bool
+ Contacts map[string]string
+ }
+ retcod, result := makeResult(RET_SUCCESS, "")
+ t, err := template.ParseFiles(
+ PATH_VIEWS+"base.html",
+ PATH_VIEWS+"contact.html",
+ )
+ if err != nil {
+ panic("error: load contact.html")
+ }
+ user := SESSIONS.Get(r)
+ if user == nil {
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+ if r.Method == "POST" && r.FormValue("switchf2f") != "" {
+ DATABASE.SwitchF2F(user)
+ }
+ if r.Method == "POST" && r.FormValue("append") != "" {
+ name := r.FormValue("nickname")
+ publ := gp.StringToPublicKey(r.FormValue("public_key"))
+ err := DATABASE.SetContact(user, name, publ)
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER,
+ fmt.Sprintf("error: %s", err.Error()))
+ goto close
+ }
+ }
+ if r.Method == "POST" && r.FormValue("delete") != "" {
+ publ := gp.StringToPublicKey(r.FormValue("public_key"))
+ err := DATABASE.DelContact(user, publ)
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER,
+ fmt.Sprintf("error: %s", err.Error()))
+ goto close
+ }
+ }
+close:
+ t.Execute(w, ContactTemplateResult{
+ TemplateResult: TemplateResult{
+ Auth: getName(SESSIONS.Get(r)),
+ Result: result,
+ Return: retcod,
+ },
+ F2F: DATABASE.StateF2F(user),
+ Contacts: DATABASE.GetContacts(user),
+ })
+}
+
+func networkConnectPage(w http.ResponseWriter, r *http.Request) {
+ type ConnTemplateResult struct {
+ TemplateResult
+ Connects [][2]string
+ }
+ retcod, result := makeResult(RET_SUCCESS, "")
+ t, err := template.ParseFiles(
+ PATH_VIEWS+"base.html",
+ PATH_VIEWS+"connect.html",
+ )
+ if err != nil {
+ panic("error: load connect.html")
+ }
+ user := SESSIONS.Get(r)
+ if user == nil {
+ http.Redirect(w, r, "/", 302)
+ return
+ }
+ if r.Method == "POST" && r.FormValue("check") != "" {
+ conns := DATABASE.GetConns(user)
+ for _, conn := range conns {
+ ret, res := checkConnection(conn)
+ if ret != RET_SUCCESS {
+ result += res
+ retcod = RET_WARNING
+ }
+ }
+ if retcod != RET_SUCCESS {
+ goto close
+ }
+ result = "success: all connections work"
+ }
+ if r.Method == "POST" && r.FormValue("append") != "" {
+ host := r.FormValue("hostname")
+ pasw := r.FormValue("password")
+ err := DATABASE.SetConn(user, host, pasw)
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER,
+ fmt.Sprintf("error: %s", err.Error()))
+ goto close
+ }
+ }
+ if r.Method == "POST" && r.FormValue("delete") != "" {
+ host := r.FormValue("hostname")
+ err := DATABASE.DelConn(user, host)
+ if err != nil {
+ retcod, result = makeResult(RET_DANGER,
+ fmt.Sprintf("error: %s", err.Error()))
+ goto close
+ }
+ }
+close:
+ t.Execute(w, ConnTemplateResult{
+ TemplateResult: TemplateResult{
+ Auth: getName(SESSIONS.Get(r)),
+ Result: result,
+ Return: retcod,
+ },
+ Connects: DATABASE.GetConns(user),
+ })
+}
+
+func checkConnection(conn [2]string) (int, string) {
+ type Resp struct {
+ Result string `json:"result"`
+ Return int `json:"return"`
+ }
+ type Req struct {
+ Macp string `json:"macp"`
+ }
+ var servresp Resp
+ pasw := gp.HashSum([]byte(conn[1]))
+ macp := gp.EncryptAES(pasw, []byte(TMESSAGE))
+ resp, err := HTCLIENT.Post(
+ "http://"+conn[0]+"/",
+ "application/json",
+ bytes.NewReader(serialize(Req{
+ Macp: gp.Base64Encode(macp),
+ })),
+ )
+ if err != nil {
+ return makeResult(RET_DANGER,
+ fmt.Sprintf("%s='%s';\n", conn[0], "error: connect"))
+ }
+ if resp.ContentLength > int64(MAXESIZE) {
+ return makeResult(RET_DANGER,
+ fmt.Sprintf("%s='%s';\n", conn[0], "error: max size"))
+ }
+ err = json.NewDecoder(resp.Body).Decode(&servresp)
+ resp.Body.Close()
+ if err != nil {
+ return makeResult(RET_DANGER,
+ fmt.Sprintf("%s='%s';\n", conn[0], "error: parse json"))
+ }
+ if servresp.Return != 0 {
+ return makeResult(RET_DANGER,
+ fmt.Sprintf("%s='%s';\n", conn[0], servresp.Result))
+ }
+ return makeResult(RET_SUCCESS, "")
+}
+
+func writeEmails(addr string, rdata []byte) {
+ type Resp struct {
+ Result string `json:"result"`
+ Return int `json:"return"`
+ }
+ type Req struct {
+ Recv string `json:"recv"`
+ Data int `json:"data"`
+ }
+ var servresp Resp
+ resp, err := HTCLIENT.Post(
+ addr+"/email/send",
+ "application/json",
+ bytes.NewReader(rdata),
+ )
+ if err != nil {
+ return
+ }
+ if resp.ContentLength > int64(MAXESIZE) {
+ return
+ }
+ err = json.NewDecoder(resp.Body).Decode(&servresp)
+ resp.Body.Close()
+ if err != nil {
+ return
+ }
+ if servresp.Return != 0 {
+ return
+ }
+}
+
+func readEmails(user *User, addr string) {
+ type Resp struct {
+ Result string `json:"result"`
+ Return int `json:"return"`
+ }
+ type Req struct {
+ Recv string `json:"recv"`
+ Data int `json:"data"`
+ }
+ var servresp Resp
+ client := gp.NewClient(user.Priv, nil)
+ pbhash := gp.HashPublicKey(client.PublicKey())
+ // GET SIZE EMAILS
+ resp, err := HTCLIENT.Post(
+ addr+"/email/recv",
+ "application/json",
+ bytes.NewReader(serialize(Req{
+ Recv: pbhash,
+ Data: 0,
+ })),
+ )
+ if err != nil {
+ return
+ }
+ if resp.ContentLength > int64(MAXESIZE) {
+ return
+ }
+ err = json.NewDecoder(resp.Body).Decode(&servresp)
+ resp.Body.Close()
+ if err != nil {
+ return
+ }
+ if servresp.Return != 0 {
+ return
+ }
+ // GET DATA EMAILS
+ size, err := strconv.Atoi(servresp.Result)
+ if err != nil {
+ return
+ }
+ for i, count := 1, 0; i <= size; i++ {
+ resp, err := HTCLIENT.Post(
+ addr+"/email/recv",
+ "application/json",
+ bytes.NewReader(serialize(Req{
+ Recv: pbhash,
+ Data: i,
+ })),
+ )
+ if err != nil {
+ break
+ }
+ if resp.ContentLength > int64(MAXESIZE) {
+ break
+ }
+ err = json.NewDecoder(resp.Body).Decode(&servresp)
+ resp.Body.Close()
+ if err != nil {
+ break
+ }
+ if servresp.Return != 0 {
+ continue
+ }
+ pack := gp.DeserializePackage([]byte(servresp.Result))
+ if pack == nil {
+ continue
+ }
+ pack = client.Decrypt(pack, POWSDIFF)
+ if pack == nil {
+ continue
+ }
+ err = DATABASE.SetEmail(user, pack)
+ if err == nil {
+ count++
+ }
+ if count == MAXCOUNT {
+ break
+ }
+ }
+}
+
+func getTexts(email *Email) [2]string {
+ head := strings.Split(email.Head, FSEPARAT)[0]
+ body := strings.Split(email.Body, FSEPARAT)[0]
+ return [2]string{
+ head,
+ body,
+ }
+}
+
+func getFiles(email *Email) [][2]string {
+ var list [][2]string
+ name := strings.Split(email.Head, FSEPARAT)[1:]
+ data := strings.Split(email.Body, FSEPARAT)[1:]
+ for i := range name {
+ list = append(list, [2]string{
+ name[i],
+ data[i],
+ })
+ }
+ return list
+}
+
+func newEmail(sender, head, body string) *gp.Package {
+ return gp.NewPackage(IS_EMAIL, serialize(Email{
+ SenderName: sender,
+ Head: head,
+ Body: body,
+ }))
+}
+
+func getName(user *User) string {
+ if user == nil {
+ return ""
+ }
+ return user.Name
+}
+
+func makeResult(retcod int, result string) (int, string) {
+ return retcod, result
+}
diff --git a/cmodels.go b/cmodels.go
index 5277303..1c46e4b 100644
--- a/cmodels.go
+++ b/cmodels.go
@@ -1,35 +1,35 @@
-package main
-
-import (
- "crypto/rsa"
- "database/sql"
- _ "github.com/mattn/go-sqlite3"
- "sync"
-)
-
-type DB struct {
- ptr *sql.DB
- mtx sync.Mutex
-}
-
-type Sessions struct {
- mpn map[string]*sessionData
- mtx sync.Mutex
-}
-
-type User struct {
- Id int
- Name string
- Pasw []byte
- Priv *rsa.PrivateKey
-}
-
-type Email struct {
- Id int
- SenderName string
- SenderPubl string
- Head string
- Body string
- Hash string
- Time string
-}
+package main
+
+import (
+ "crypto/rsa"
+ "database/sql"
+ _ "github.com/mattn/go-sqlite3"
+ "sync"
+)
+
+type DB struct {
+ ptr *sql.DB
+ mtx sync.Mutex
+}
+
+type Sessions struct {
+ mpn map[string]*sessionData
+ mtx sync.Mutex
+}
+
+type User struct {
+ Id int
+ Name string
+ Pasw []byte
+ Priv *rsa.PrivateKey
+}
+
+type Email struct {
+ Id int
+ SenderName string
+ SenderPubl string
+ Head string
+ Body string
+ Hash string
+ Time string
+}
diff --git a/compile.c b/compile.c
new file mode 100644
index 0000000..17d0ef0
--- /dev/null
+++ b/compile.c
@@ -0,0 +1,47 @@
+#include