]> WPIA git - cassiopeia.git/blob - src/db/psql.cpp
cdbce22393a253c00734008feb4e75dde5742c8a
[cassiopeia.git] / src / db / psql.cpp
1 #include "psql.h"
2
3 #include <stdio.h>
4
5 #include <iostream>
6
7 #include <log/logger.hpp>
8 #include <exception>
9
10 PostgresJobProvider::PostgresJobProvider( const std::string& server, const std::string& user, const std::string& password, const std::string& database ):
11     c( "dbname=" + database + " host=" + server + " user=" + user + " password=" + password + " client_encoding=UTF-8 application_name=cassiopeia-client" ) {
12     // TODO better connection string generation??
13     pqxx::work txn( c );
14     pqxx::result version = txn.exec( "SELECT \"version\" FROM \"schemeVersion\"" );
15
16     if( version.size() != 1 ) {
17         throw std::runtime_error( "Only one version row expected but multiple found." );
18     }
19
20     if( version[0][0].as<int>() < 33 ) {
21         throw std::runtime_error( "Requires at least database schema version 33. Please update gigi before restarting cassiopeia." );
22     }
23 }
24
25
26 std::shared_ptr<Job> PostgresJobProvider::fetchJob() {
27     std::string q = "SELECT id, \"targetId\", task, \"executeFrom\", \"executeTo\", attempt FROM jobs WHERE state='open' AND attempt < 3";
28     pqxx::work txn( c );
29     pqxx::result result = txn.exec( q );
30
31
32     auto job = std::make_shared<Job>();
33
34     if( result.size() == 0 ) {
35         return nullptr;
36     }
37
38     job->id = result[0]["id"].as<std::string>();
39     job->target =  result[0]["\"targetId\""].as<std::string>();
40     job->task = result[0]["task"].as<std::string>();
41     job->from = result[0]["\"executeFrom\""].as<std::string>( "" );
42     job->to = result[0]["\"executeTo\""].as<std::string>( "" );
43     job->attempt = result[0]["attempt"].as<std::string>();
44
45     logger::notef( "Got a job: (id=%s, target=%s, task=%s, from=%s, to=%s, attempts=%s)", job->id, job->target, job->task, job->from, job->to, job->attempt );
46     return job;
47 }
48
49 void PostgresJobProvider::finishJob( std::shared_ptr<Job> job ) {
50     pqxx::work txn( c );
51
52     std::string q = "UPDATE jobs SET state='done' WHERE id=" + txn.quote( job->id );
53     pqxx::result r = txn.exec( q );
54
55     if( r.affected_rows() != 1 ) {
56         throw std::runtime_error( "No database entry found." );
57     }
58
59     c.prepare( "insertLog", "INSERT INTO \"jobLog\"(\"jobid\", \"attempt\", \"content\") VALUES($1,$2,$3)" );
60     txn.prepared( "insertCrt" )( job->id )( job->attempt )( job->log.str() ).exec();
61
62     txn.commit();
63 }
64
65 void PostgresJobProvider::failJob( std::shared_ptr<Job> job ) {
66     pqxx::work txn( c );
67
68     std::string q = "UPDATE jobs SET attempt = attempt + 1 WHERE id=" + txn.quote( job->id );
69     pqxx::result r = txn.exec( q );
70
71     if( r.affected_rows() != 1 ) {
72         throw std::runtime_error( "No database entry found." );
73     }
74
75     c.prepare( "insertLog", "INSERT INTO \"jobLog\"(\"jobid\", \"attempt\", \"content\") VALUES($1,$2,$3)" );
76     txn.prepared( "insertCrt" )( job->id )( job->attempt )( job->log.str() ).exec();
77
78     txn.commit();
79 }
80
81 std::shared_ptr<TBSCertificate> PostgresJobProvider::fetchTBSCert( std::shared_ptr<Job> job ) {
82     pqxx::work txn( c );
83     auto cert = std::make_shared<TBSCertificate>();
84     std::string q = "SELECT md, profile, csr_type, keyname, att.content AS csr FROM certs INNER JOIN profiles ON profiles.id = certs.profile INNER JOIN \"certificateAttachment\" att ON att.certid=certs.id AND att.type='CSR' WHERE certs.id=" + txn.quote( job->target );
85     pqxx::result r = txn.exec( q );
86
87     if( r.size() != 1 ) {
88         throw std::runtime_error( "Error, no or multiple certs found" );
89     }
90
91     auto ro = r[0];
92
93     std::string profileName = ro["keyname"].as<std::string>();
94
95     cert->md = ro["md"].as<std::string>();
96     std::string profileId = ro["profile"].as<std::string>();
97
98     while( profileId.size() < 4 ) {
99         profileId = "0" + profileId;
100     }
101
102     cert->profile = profileId + "-" + profileName;
103
104     cert->csr_content = ro["csr"].as<std::string>();
105     cert->csr_type = ro["csr_type"].as<std::string>();
106
107     cert->SANs = std::vector<std::shared_ptr<SAN>>();
108
109     q = "SELECT contents, type FROM \"subjectAlternativeNames\" WHERE \"certId\"=" + txn.quote( job->target );
110     r = txn.exec( q );
111
112     std::cout << "Fetching SANs" << std::endl;
113
114     for( auto row = r.begin(); row != r.end(); ++row ) {
115         auto nSAN = std::make_shared<SAN>();
116         nSAN->content = row["contents"].as<std::string>();
117         nSAN->type = row["type"].as<std::string>();
118         cert->SANs.push_back( nSAN );
119     }
120
121     q = "SELECT name, value FROM \"certAvas\" WHERE \"certId\"=" + txn.quote( job->target );
122     r = txn.exec( q );
123
124     for( auto row = r.begin(); row != r.end(); ++row ) {
125         auto nAVA = std::make_shared<AVA>();
126         nAVA->name = row["name"].as<std::string>();
127         nAVA->value = row["value"].as<std::string>();
128         cert->AVAs.push_back( nAVA );
129     }
130
131     return cert;
132 }
133
134 std::string pgTime( std::string isoTime ) {
135     return isoTime.substr( 0, 8 ) + " " + isoTime.substr( 8, 6 );
136 }
137
138 void PostgresJobProvider::writeBack( std::shared_ptr<Job> job, std::shared_ptr<SignedCertificate> res ) {
139     pqxx::work txn( c );
140     std::string id = "SELECT id FROM cacerts WHERE keyname=" + txn.quote( res->ca_name );
141     pqxx::result r = txn.exec( id );
142
143     std::string read_id;
144
145     if( r.size() != 1 ) {
146         throw std::runtime_error( "Error while inserting new ca cert not found" );
147     } else {
148         read_id = r[0]["id"].as<std::string>();
149     }
150
151     std::string serial = res->serial;
152     std::transform( serial.begin(), serial.end(), serial.begin(), ::tolower );
153
154     if( serial[0] == '0' ) {
155         serial = serial.substr( 1 );
156     }
157
158     std::string q = "UPDATE certs SET serial=" + txn.quote( serial ) + ", \"caid\" = " + txn.quote( read_id ) + ", created=" + txn.quote( pgTime( res->before ) ) + ", expire=" + txn.quote( pgTime( res->after ) ) + "  WHERE id=" + txn.quote( job->target );
159     // TODO write more thingies back
160
161     r = txn.exec( q );
162
163     if( r.affected_rows() != 1 ) {
164         throw std::runtime_error( "Only one row should be updated." );
165     }
166
167     c.prepare( "insertCrt", "INSERT INTO \"certificateAttachment\"(\"certid\", \"type\", \"content\") VALUES($1,'CRT',$2)" );
168     txn.prepared( "insertCrt" )( job->target )( res->certificate ).exec();
169
170     txn.commit();
171 }
172
173 std::pair<std::string, std::string> PostgresJobProvider::getRevocationInfo( std::shared_ptr<Job> job ) {
174     pqxx::work txn( c );
175     std::string q = "SELECT certs.serial, cacerts.keyname FROM certs INNER JOIN cacerts ON certs.\"caid\" = cacerts.id WHERE certs.id = " + txn.quote( job->target );
176
177     pqxx::result r = txn.exec( q );
178
179     if( r.size() != 1 ) {
180         throw std::runtime_error( "Only one row expected but multiple found." );
181     }
182
183     return {r[0][0].as<std::string>(), r[0][1].as<std::string>()};
184 }
185
186 void PostgresJobProvider::writeBackRevocation( std::shared_ptr<Job> job, std::string date ) {
187     logger::notef( "Revoking at %s", date );
188     pqxx::work txn( c );
189     logger::note( "executing" );
190     pqxx::result r = txn.exec( "UPDATE certs SET revoked = " + txn.quote( pgTime( date ) ) + " WHERE id = " + txn.quote( job->target ) );
191
192     if( r.affected_rows() != 1 ) {
193         throw std::runtime_error( "Only one row should be updated." );
194     }
195
196     logger::note( "committing" );
197     txn.commit();
198     logger::note( "committed" );
199 }