1use anyhow::{Context, Result};
10use diesel::prelude::*;
11use diesel::result::Error;
12
13pub mod models;
14mod schema;
15
16use models::*;
17use schema::*;
18
19use crate::pgp;
20
21pub(crate) struct OcaDb {
23 url: String,
24 conn: SqliteConnection,
25}
26
27impl OcaDb {
28 pub(crate) fn new(db_url: &str) -> Result<Self> {
29 let conn =
30 SqliteConnection::establish(db_url).context(format!("Error connecting to {db_url}"))?;
31
32 diesel::sql_query("PRAGMA foreign_keys=1;")
34 .execute(&conn)
35 .context("Couldn't set 'PRAGMA foreign_keys=1;'")?;
36
37 Ok(OcaDb {
38 conn,
39 url: db_url.to_string(),
40 })
41 }
42
43 pub(crate) fn url(&self) -> &str {
44 &self.url
45 }
46
47 pub(crate) fn transaction<T, E, F>(&self, f: F) -> Result<T, E>
48 where
49 F: FnOnce() -> Result<T, E>,
50 E: From<Error>,
51 {
52 self.conn.transaction(f)
53 }
54
55 pub(crate) fn vacuum(&self) -> Result<()> {
63 diesel::sql_query("VACUUM;")
64 .execute(&self.conn)
65 .context("Error while running 'VACUUM;'")?;
66
67 Ok(())
68 }
69
70 fn user_insert(&self, user: NewUser) -> Result<User> {
73 let inserted_count = diesel::insert_into(users::table)
74 .values(&user)
75 .execute(&self.conn)?;
76
77 if inserted_count != 1 {
78 return Err(anyhow::anyhow!(
79 "insert_user: insert should return count '1'"
80 ));
81 }
82
83 let u: Vec<User> = users::table
85 .order(users::id.desc())
86 .limit(inserted_count as i64)
87 .load(&self.conn)?
88 .into_iter()
89 .rev()
90 .collect();
91
92 if u.len() == 1 {
93 Ok(u[0].clone())
94 } else {
95 Err(anyhow::anyhow!("insert_user: unexpected insert failure"))
96 }
97 }
98
99 fn cert_insert(&self, cert: NewCert) -> Result<Cert> {
100 let inserted_count = diesel::insert_into(certs::table)
101 .values(&cert)
102 .execute(&self.conn)?;
103
104 if inserted_count != 1 {
105 return Err(anyhow::anyhow!(
106 "insert_cert: insert should return count '1'"
107 ));
108 }
109
110 let c: Vec<Cert> = certs::table
112 .order(certs::id.desc())
113 .limit(inserted_count as i64)
114 .load(&self.conn)?
115 .into_iter()
116 .rev()
117 .collect();
118
119 if c.len() == 1 {
120 Ok(c[0].clone())
121 } else {
122 Err(anyhow::anyhow!("insert_cert: unexpected insert failure"))
123 }
124 }
125
126 fn revocation_insert(&self, revoc: NewRevocation) -> Result<Revocation> {
127 let inserted_count = diesel::insert_into(revocations::table)
128 .values(&revoc)
129 .execute(&self.conn)?;
130
131 if inserted_count != 1 {
132 return Err(anyhow::anyhow!(
133 "insert_revocation: insert should return count '1'"
134 ));
135 }
136
137 let r: Vec<Revocation> = revocations::table
139 .order(revocations::id.desc())
140 .limit(inserted_count as i64)
141 .load(&self.conn)?
142 .into_iter()
143 .rev()
144 .collect();
145
146 if r.len() == 1 {
147 Ok(r[0].clone())
148 } else {
149 Err(anyhow::anyhow!(
150 "insert_revocation: unexpected insert failure"
151 ))
152 }
153 }
154
155 fn email_insert(&self, email: NewCertEmail) -> Result<CertEmail> {
156 let inserted_count = diesel::insert_into(certs_emails::table)
157 .values(&email)
158 .execute(&self.conn)
159 .context("Error saving new email")?;
160
161 if inserted_count != 1 {
162 return Err(anyhow::anyhow!(
163 "insert_email: insert should return count '1'"
164 ));
165 }
166
167 let e: Vec<CertEmail> = certs_emails::table
169 .order(certs_emails::id.desc())
170 .limit(inserted_count as i64)
171 .load(&self.conn)?
172 .into_iter()
173 .collect();
174
175 if e.len() != 1 {
176 return Err(anyhow::anyhow!(
177 "insert_email: unexpected insert failure [emails]"
178 ));
179 }
180
181 Ok(e[0].clone())
182 }
183
184 pub(crate) fn queue_by_id(&self, id: i32) -> Result<Option<Queue>> {
185 let mut db: Vec<Queue> = queue::table
186 .filter(queue::id.eq(id))
187 .load::<Queue>(&self.conn)
188 .context("Error loading Queue by id")?;
189
190 match db.len() {
191 0 => Ok(None),
192 1 => Ok(Some(db.pop().unwrap())),
193 _ => Err(anyhow::anyhow!("queue_by_id: expected 0 or 1 entries")),
194 }
195 }
196
197 pub(crate) fn queue_insert(&self, q: NewQueue) -> Result<()> {
198 let inserted_count = diesel::insert_into(queue::table)
199 .values(&q)
200 .execute(&self.conn)
201 .context("Error saving new queue entry")?;
202
203 if inserted_count != 1 {
204 return Err(anyhow::anyhow!(
205 "queue_insert: insert should return count '1'"
206 ));
207 }
208
209 Ok(())
210 }
211
212 pub(crate) fn queue_not_done(&self) -> Result<Vec<Queue>> {
214 queue::table
215 .filter(queue::done.eq(false))
216 .order(queue::id)
217 .load::<Queue>(&self.conn)
218 .context("Error loading queue entries")
219 }
220
221 pub(crate) fn queue_update(&self, queue: &Queue) -> Result<()> {
222 diesel::update(queue)
223 .set(queue)
224 .execute(&self.conn)
225 .context("Error updating Queue")?;
226
227 Ok(())
228 }
229
230 pub(crate) fn is_ca_initialized(&self) -> Result<bool> {
233 let cas = cas::table
234 .load::<Ca>(&self.conn)
235 .context("Error loading CAs")?;
236
237 Ok(cas.len() == 1)
238 }
239
240 pub(crate) fn get_ca(&self) -> Result<(Ca, Cacert)> {
241 let cas = cas::table
242 .load::<Ca>(&self.conn)
243 .context("Error loading CAs")?;
244
245 match cas.len() {
246 0 => Err(anyhow::anyhow!("CA is not initialized")),
247 1 => {
248 let ca = cas[0].clone();
249
250 let ca_certs: Vec<_> = cacerts::table
251 .filter(cacerts::ca_id.eq(ca.id))
252 .filter(cacerts::active)
253 .load::<Cacert>(&self.conn)
254 .context("Error loading CA Certs")?;
255
256 match ca_certs.len() {
257 0 => Err(anyhow::anyhow!("No CA cert found")),
258 1 => Ok((ca, ca_certs[0].to_owned())),
259 _ => {
260 Err(anyhow::anyhow!(
264 "More than one active cacert in DB, illegal state."
265 ))
266 }
267 }
268 }
269 _ => Err(anyhow::anyhow!(
270 "More than one CA in database, this should never happen."
271 )),
272 }
273 }
274
275 pub(crate) fn ca_insert(
276 &self,
277 domainname: &str,
278 ca_key: &str,
279 fingerprint: &str,
280 backend: Option<&str>,
281 ) -> Result<()> {
282 let ca = NewCa { domainname };
283
284 diesel::insert_into(cas::table)
285 .values(&ca)
286 .execute(&self.conn)
287 .context("Error saving new CA")?;
288
289 let cas = cas::table
291 .load::<Ca>(&self.conn)
292 .context("Error loading CAs")?;
293 let ca = cas.first().unwrap();
294
295 let ca_cert = NewCacert {
297 fingerprint,
298 ca_id: ca.id,
299 priv_cert: ca_key.to_string(),
300 backend,
301 active: true,
302 };
303 self.cacert_insert(&ca_cert)?;
304
305 Ok(())
306 }
307
308 pub(crate) fn cacerts_delete(&self) -> Result<()> {
309 diesel::delete(cacerts::table)
310 .execute(&self.conn)
311 .context("Error while deleting cacerts entries")?;
312 Ok(())
313 }
314
315 pub(crate) fn cacert_insert(&self, ca_cert: &NewCacert) -> Result<()> {
316 diesel::insert_into(cacerts::table)
317 .values(ca_cert)
318 .execute(&self.conn)
319 .context("Error saving new CA Cert")?;
320
321 Ok(())
322 }
323
324 pub(crate) fn cacert_update(&self, cacert: &Cacert) -> Result<()> {
331 diesel::update(cacert)
332 .set(cacert)
333 .execute(&self.conn)
334 .context("Error updating CaCert")?;
335
336 Ok(())
337 }
338
339 pub(crate) fn ca_import_tsig(&self, cert: &[u8]) -> Result<()> {
344 let (_, mut ca_cert) = self
345 .get_ca()
346 .context("Failed to load CA cert from database")?;
347 let ca = pgp::to_cert(ca_cert.priv_cert.as_bytes())?;
348
349 let cert_import = pgp::to_cert(cert)?;
350
351 let joined = pgp::merge_in_tsigs(ca, cert_import)?;
352
353 ca_cert.priv_cert =
355 pgp::cert_to_armored_private_key(&joined).context("Failed to re-armor CA Cert")?;
356
357 self.cacert_update(&ca_cert)
358 .context("Update of CA Cert in DB failed")
359 }
360
361 pub(crate) fn users_sorted_by_name(&self) -> Result<Vec<User>> {
362 users::table
363 .order((users::name, users::id))
364 .load::<User>(&self.conn)
365 .context("Error loading users")
366 }
367
368 pub(crate) fn user_by_cert(&self, cert: &Cert) -> Result<Option<User>> {
369 match cert.user_id {
370 None => Ok(None),
371 Some(search_id) => {
372 let users = users::table
373 .filter(users::id.eq(search_id))
374 .load::<User>(&self.conn)?;
375
376 match users.len() {
377 0 => Ok(None),
378 1 => Ok(Some(users[0].clone())),
379 _ => {
380 Err(anyhow::anyhow!(
382 "get_user_by_cert: Found more than one user for cert"
383 ))
384 }
385 }
386 }
387 }
388 }
389
390 pub(crate) fn user_add(
391 &self,
392 name: Option<&str>,
393 (pub_cert, fingerprint): (&str, &str),
394 emails: &[&str],
395 revocation_certs: &[String],
396 ) -> Result<User> {
397 let (ca, _) = self.get_ca().context("Couldn't find CA")?;
399
400 let user = self.user_insert(NewUser { name, ca_id: ca.id })?;
401
402 let cert = self.cert_add(pub_cert, fingerprint, Some(user.id))?;
403
404 for revocation in revocation_certs {
406 let hash = &pgp::revocation_to_hash(revocation.as_bytes())?;
407 self.revocation_insert(NewRevocation {
408 hash,
409 revocation,
410 cert_id: cert.id,
411 published: false,
412 })?;
413 }
414
415 for &addr in emails {
417 self.email_insert(NewCertEmail {
418 addr: addr.to_owned(),
419 cert_id: cert.id,
420 })?;
421 }
422 Ok(user)
423 }
424
425 #[allow(dead_code)]
426 pub(crate) fn user_update(&self, user: &User) -> Result<()> {
427 diesel::update(user)
428 .set(user)
429 .execute(&self.conn)
430 .context("Error updating User")?;
431
432 Ok(())
433 }
434
435 pub(crate) fn cert_add(
436 &self,
437 pub_cert: &str,
438 fingerprint: &str,
439 user_id: Option<i32>,
440 ) -> Result<Cert> {
441 let cert = NewCert {
442 pub_cert,
443 fingerprint,
444 delisted: false,
445 inactive: false,
446 user_id,
447 };
448 self.cert_insert(cert)
449 }
450
451 pub fn cert_update(&self, cert: &Cert) -> Result<()> {
452 diesel::update(cert)
453 .set(cert)
454 .execute(&self.conn)
455 .context("Error updating Cert")?;
456
457 Ok(())
458 }
459
460 pub fn cert_by_id(&self, id: i32) -> Result<Option<Cert>> {
461 let db: Vec<Cert> = certs::table
462 .filter(certs::id.eq(id))
463 .load::<Cert>(&self.conn)
464 .context("Error loading Cert by id")?;
465
466 Ok(db.first().cloned())
467 }
468
469 pub(crate) fn cert_by_fp(&self, fingerprint: &str) -> Result<Option<Cert>> {
470 let c = certs::table
471 .filter(certs::fingerprint.eq(fingerprint))
472 .load::<Cert>(&self.conn)
473 .context("Error loading Cert by fingerprint")?;
474
475 match c.len() {
476 0 => Ok(None),
477 1 => Ok(Some(c[0].clone())),
478 _ => Err(anyhow::anyhow!("get_cert: expected 0 or 1 cert")),
479 }
480 }
481
482 pub(crate) fn certs_by_email(&self, email: &str) -> Result<Vec<Cert>> {
483 let cert_ids = certs_emails::table
484 .filter(certs_emails::addr.eq(email))
485 .select(certs_emails::cert_id);
486
487 certs::table
488 .filter(certs::id.eq_any(cert_ids))
489 .load::<Cert>(&self.conn)
490 .context("could not load certs")
491 }
492
493 pub(crate) fn certs_by_user(&self, user: &User) -> Result<Vec<Cert>> {
495 Ok(Cert::belonging_to(user)
496 .order(certs::id)
497 .load::<Cert>(&self.conn)?)
498 }
499
500 pub(crate) fn certs(&self) -> Result<Vec<Cert>> {
502 certs::table
503 .load::<Cert>(&self.conn)
504 .context("Error loading certs")
505 }
506
507 pub(crate) fn revocations_by_cert(&self, cert: &Cert) -> Result<Vec<Revocation>> {
508 Ok(Revocation::belonging_to(cert).load::<Revocation>(&self.conn)?)
509 }
510
511 pub(crate) fn revocation_add(&self, revocation: &str, cert: &Cert) -> Result<Revocation> {
512 let hash = &pgp::revocation_to_hash(revocation.as_bytes())?;
513
514 self.revocation_insert(NewRevocation {
515 hash,
516 revocation,
517 cert_id: cert.id,
518 published: false,
519 })
520 }
521
522 pub(crate) fn revocation_exists(&self, revocation: &[u8]) -> Result<bool> {
524 let hash = &pgp::revocation_to_hash(revocation)?;
525 Ok(self.revocation_by_hash(hash)?.is_some())
526 }
527
528 pub(crate) fn revocation_by_hash(&self, hash: &str) -> Result<Option<Revocation>> {
529 let db: Vec<Revocation> = revocations::table
530 .filter(revocations::hash.eq(hash))
531 .load::<Revocation>(&self.conn)
532 .context("Error loading Revocation by hash")?;
533
534 assert!(
535 db.len() <= 1,
536 "unexpected duplicate hash in revocations table"
537 );
538
539 Ok(db.first().cloned())
540 }
541
542 pub(crate) fn revocation_update(&self, revocation: &Revocation) -> Result<()> {
543 diesel::update(revocation)
544 .set(revocation)
545 .execute(&self.conn)
546 .context("Error updating Revocation")?;
547
548 Ok(())
549 }
550
551 pub(crate) fn emails_by_cert(&self, cert: &Cert) -> Result<Vec<CertEmail>> {
552 certs_emails::table
553 .filter(certs_emails::cert_id.eq(cert.id))
554 .load(&self.conn)
555 .context("could not load emails")
556 }
557
558 pub(crate) fn emails(&self) -> Result<Vec<CertEmail>> {
559 certs_emails::table
560 .load(&self.conn)
561 .context("could not load emails")
562 }
563
564 pub(crate) fn bridge_insert(&self, bridge: NewBridge) -> Result<Bridge> {
565 let inserted_count = diesel::insert_into(bridges::table)
566 .values(&bridge)
567 .execute(&self.conn)
568 .context("Error saving new bridge")?;
569
570 if inserted_count != 1 {
571 return Err(anyhow::anyhow!(
572 "insert_user: insert should return count '1'"
573 ));
574 }
575
576 let b: Vec<Bridge> = bridges::table
577 .order(bridges::id.desc())
578 .limit(inserted_count as i64)
579 .load(&self.conn)?
580 .into_iter()
581 .rev()
582 .collect();
583
584 if b.len() == 1 {
585 Ok(b[0].clone())
586 } else {
587 Err(anyhow::anyhow!("insert_user: unexpected insert failure"))
588 }
589 }
590
591 pub(crate) fn bridge_by_email(&self, email: &str) -> Result<Option<Bridge>> {
592 let res = bridges::table
593 .filter(bridges::email.eq(email))
594 .load::<Bridge>(&self.conn)
595 .context("Error loading bridge")?;
596
597 match res.len() {
598 0 => Ok(None),
599 1 => Ok(Some(res[0].clone())),
600 _ => Err(anyhow::anyhow!(format!(
601 "search_bridge for {} found {} results, expected <=1. \
602 (Database constraints should make this impossible)",
603 email,
604 res.len()
605 ))),
606 }
607 }
608
609 pub(crate) fn list_bridges(&self) -> Result<Vec<Bridge>> {
610 bridges::table
611 .load::<Bridge>(&self.conn)
612 .context("Error loading bridges")
613 }
614
615 pub(crate) fn diesel_migrations_run(&self) {
616 embed_migrations!();
617
618 embedded_migrations::run(&self.conn).unwrap_or_else(|e| {
619 panic!("failed to configure database, error {}", e);
620 });
621 }
622}