forked from dcposch/scramble
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrepo.go
269 lines (245 loc) · 6.01 KB
/
repo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
package main
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
import (
"io/ioutil"
"log"
"os"
"strings"
"time"
)
var db *sql.DB
func init() {
// read configuration
configFile := os.Getenv("HOME") + "/.scramble/db.config"
mysqlHostBytes, err := ioutil.ReadFile(configFile)
if err != nil {
log.Panicf("Please create config file %s.\n"+
"One line, <db user>:<db pass>@<db host, empty if local>/scramble",
configFile)
}
// connect to the database, ping periodically to maintain the connection
mysqlHost := strings.TrimSpace(string(mysqlHostBytes)) + "?charset=utf8"
log.Printf("Connecting to %s\n", mysqlHost)
db, err = sql.Open("mysql", mysqlHost)
if err != nil {
panic(err)
}
go ping()
// migrate the database
migrateDb()
}
func ping() {
ticker := time.Tick(time.Minute)
for {
<-ticker
err := db.Ping()
if err != nil {
log.Printf("DB not ok: %v\n", err)
} else {
log.Printf("DB ok\n")
}
}
}
//
// USERS
//
func SaveUser(user *User) bool {
res, err := db.Exec("insert ignore into user"+
" (token, password_hash, public_hash, public_key, cipher_private_key)"+
" values (?, ?, ?, ?, ?)",
user.Token, user.PasswordHash,
user.PublicHash, user.PublicKey,
user.CipherPrivateKey)
if err != nil {
panic(err)
}
nrows, err := res.RowsAffected()
if err != nil {
panic(err)
}
return nrows == 1
}
func LoadUser(token string) *User {
var user User
user.Token = token
err := db.QueryRow("select"+
" password_hash, password_hash_old, public_hash, public_key, cipher_private_key"+
" from user where token=?", token).Scan(
&user.PasswordHash,
&user.PasswordHashOld,
&user.PublicHash,
&user.PublicKey,
&user.CipherPrivateKey)
if err == sql.ErrNoRows {
return nil
}
if err != nil {
panic(err)
}
return &user
}
func LoadUserID(token string) *UserID {
var user UserID
user.Token = token
err := db.QueryRow("select"+
" password_hash, password_hash_old, public_hash"+
" from user where token=?", token).Scan(
&user.PasswordHash,
&user.PasswordHashOld,
&user.PublicHash)
if err == sql.ErrNoRows {
return nil
}
if err != nil {
panic(err)
}
return &user
}
// Loads a given public key by it's hash
// The client then verifies that the key is correct
func LoadPubKey(publicHash string) string {
var publicKey string
err := db.QueryRow("select public_key"+
" from user where public_hash=?",
publicHash).Scan(&publicKey)
if err == sql.ErrNoRows {
return ""
}
if err != nil {
panic(err)
}
return publicKey
}
// Loads a user's contacts, or nil if the user doesn't exist
// Returns an encrypted blob for which only they have the key
func LoadContacts(token string) *string {
var cipherContacts *string
err := db.QueryRow("select cipher_contacts"+
" from user where token=?", token).Scan(
&cipherContacts)
if err == sql.ErrNoRows {
return nil
}
if err != nil {
panic(err)
}
return cipherContacts
}
func SaveContacts(token string, cipherContacts string) {
_, err := db.Exec("update user "+
" set cipher_contacts=? where token=?",
cipherContacts, token)
if err != nil {
panic(err)
}
}
//
// EMAIL HEADERS
//
// Loads all email headers in a certain box
// For example, inbox or sent box
// That are encrypted for a given user
func LoadBox(pubHashTo string, box string) []EmailHeader {
log.Printf("Fetching %s for %s\n", box, pubHashTo)
rows, err := db.Query("select message_id, box, unix_time, "+
" from_email, to_email, pub_hash_from, pub_hash_to, cipher_subject "+
" from email where pub_hash_to=? and box=? "+
" order by unix_time desc", pubHashTo, box)
if err != nil {
panic(err)
}
return rowsToHeaders(rows)
}
func LoadSent(pubHash string) []EmailHeader {
rows, err := db.Query("select message_id, box, unix_time, "+
" from_email, to_email, pub_hash_from, pub_hash_to, cipher_subject "+
" from email where pub_hash_from=? and pub_hash_to=? "+
" order by unix_time desc", pubHash, pubHash)
if err != nil {
panic(err)
}
return rowsToHeaders(rows)
}
func rowsToHeaders(rows *sql.Rows) []EmailHeader {
// collect a short description of each email
headers := make([]EmailHeader, 0)
for rows.Next() {
var header EmailHeader
err := rows.Scan(
&header.MessageID,
&header.Box,
&header.UnixTime,
&header.From,
&header.To,
&header.PubHashFrom,
&header.PubHashTo,
&header.CipherSubject)
if err != nil {
panic(err)
}
headers = append(headers, header)
}
return headers
}
//
// EMAIL
//
// Saves a single email, encrypted for a single recipient.
// When sending one email, this function will be called multiple times.
//
// Incoming email is saved to the inbox
// Outgoing mail to an external server is saved to the outbox, waiting for SMTP
// Outgoing mail to the same domain is saved directly to that user's inbox
// All outgoing mail is also saved to the sender's sent box
func SaveMessage(e *Email) {
_, err := db.Exec("insert into email "+
"(message_id, box, unix_time, from_email, to_email, "+
" pub_hash_from, pub_hash_to, cipher_subject, cipher_body) "+
"values (?,?,?,?,?,?,?,?,?)",
e.MessageID,
e.Box,
e.UnixTime,
e.From,
e.To,
e.PubHashFrom,
e.PubHashTo,
e.CipherSubject,
e.CipherBody)
if err != nil {
panic(err)
}
}
// Retrieves a single message, by id
// Because a message will be stored multiple times, encrypted separately
// for each recipient, we must also specify the recipient hash
func LoadMessage(id string, pubHashTo string) Email {
var email Email
err := db.QueryRow("select "+
"box, unix_time, from_email, to_email, "+
"pub_hash_from, cipher_subject, cipher_body "+
"from email where message_id=? and pub_hash_to=?",
id, pubHashTo).Scan(
&email.Box,
&email.UnixTime,
&email.From,
&email.To,
&email.PubHashFrom,
&email.CipherSubject,
&email.CipherBody)
email.MessageID = id
email.PubHashTo = pubHashTo
if err != nil {
panic(err)
}
return email
}
func UpdateEmail(id string, pubHashTo string, newBox string) {
_, err := db.Exec("update email "+
"set box=? "+
"where message_id=? and pub_hash_to=?",
newBox, id, pubHashTo)
if err != nil {
panic(err)
}
}