1 /* vi: set sw=4 ts=4:
2  *
3  * Copyright (C) 2001 - 2012 Christian Hohnstaedt.
4  *
5  * All rights reserved.
6  */
7 
8 #include "db.h"
9 #include "base.h"
10 #include "func.h"
11 #include "exception.h"
12 #include <QStringList>
13 #include <QDebug>
14 #include <QDateTime>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #include <unistd.h>
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <errno.h>
23 
24 #define XNUM(n) CCHAR(QString::number((n), 16))
25 
db(const QString & filename,QFlags<QFile::Permission> perm)26 db::db(const QString &filename, QFlags<QFile::Permission> perm)
27 {
28 	name = filename;
29 	file.setFileName(filename);
30 	bool newFile = !file.exists();
31 
32 	if (!file.open(QIODevice::ReadWrite)) {
33 		fileIOerr("open");
34 	} else {
35 		first();
36 		if (newFile)
37 			file.setPermissions(perm);
38 		else if (!verify_magic()) {
39 			file.close();
40 			throw errorEx("Unknown database format", filename);
41 		}
42 	}
43 }
44 
~db()45 db::~db()
46 {
47 	file.close();
48 }
49 
fileIOerr(QString s)50 void db::fileIOerr(QString s)
51 {
52 	errstr = QString("DB ") + s + "() '" + file.fileName() + "'";
53 	dberrno = errno;
54 	throw errorEx(errstr, strerror(errno));
55 }
56 
convert_header(db_header_t * h)57 void db::convert_header(db_header_t *h)
58 {
59 	h->magic   = xntohl(head.magic);
60 	h->len     = xntohl(head.len);
61 	h->headver = xntohs(head.headver);
62 	h->type    = xntohs(head.type);
63 	h->version = xntohs(head.version);
64 	h->flags   = xntohs(head.flags);
65 	memcpy(h->name, head.name, NAMELEN);
66 }
67 
verify_magic(void)68 bool db::verify_magic(void)
69 {
70 	if (!eof())
71 		if (xntohl(head.magic) != XCA_MAGIC) {
72 			return false;
73 		}
74 	return true;
75 }
76 
eof()77 bool db::eof()
78 {
79 	return head_offset == file.size();
80 }
81 
find(enum pki_type type,QString name)82 int db::find(enum pki_type type, QString name)
83 {
84 	while (!eof()) {
85 		if (xntohs(head.type) == type) {
86 			if (name.isEmpty()) { /* only compare type */
87 				return 0;
88 			} else if (QString::fromUtf8(head.name) == name) {
89 				return 0;
90 			}
91 		}
92 		if (!verify_magic()) {
93 			return -1;
94 		}
95 		next();
96 	}
97 	return 1;
98 }
99 
first(int flag)100 void db::first(int flag)
101 {
102 	int ret;
103 	memset(&head, 0, sizeof(db_header_t) );
104 	head_offset = 0;
105 	file.seek(0);
106 	ret = file.read((char*)&head, sizeof(db_header_t) );
107 	if (ret < 0 )
108 		fileIOerr("read");
109 	if (ret==0) {
110 		head_offset = file.size();
111 		return;
112 	}
113 	if (!verify_magic())
114 		return;
115 	if (xntohs(head.flags) & flag)
116 		next(flag);
117 }
118 
next(int flag)119 int db::next(int flag)
120 {
121 	qint64 ret;
122 	qint64 garbage = -1;
123 	int result = 1;
124 
125 	if (eof())
126 		return 1;
127 
128 	head_offset += xntohl(head.len);
129 	if (head_offset >= file.size()) {
130 		head_offset = file.size();
131 		return 1;
132 	}
133 	while (1) {
134 		file.seek(head_offset);
135 		ret = file.read((char*)&head, sizeof head);
136 		if (ret==0) {
137 			head_offset = file.size();
138 			break;
139 		}
140 		if (ret < 0) {
141 			fileIOerr("read");
142 			return -1;
143 		}
144 		if (ret != sizeof head) {
145 			qWarning("next(): Short read: 0x%s of 0x%s @ 0x%s",
146 				XNUM(ret), XNUM(sizeof head),
147 				XNUM(head_offset));
148 			if (garbage != -1) {
149 				ret += head_offset - garbage;
150 				head_offset = garbage;
151 			}
152 			qWarning("next(): Truncating 0x%s garbage bytes @ 0x%s",
153 				XNUM(ret), XNUM(head_offset));
154 			if (backup())
155 				file.resize(head_offset);
156 			head_offset = file.size();
157 			return -1;
158 		}
159 		qint64 hlen = xntohl(head.len);
160 		if (!verify_magic()) {
161 			if (garbage == -1)
162 				garbage = head_offset;
163 			head_offset += 1;
164 			continue;
165 		} else {
166 			if (garbage != -1) {
167 				qWarning("next(): 0x%s bytes garbage skipped at 0x%s",
168 					XNUM(head_offset - garbage),
169 					XNUM(garbage));
170 			}
171 			garbage = -1;
172 			if (file.size() < head_offset + hlen) {
173 				qWarning("next(): Short item (%s of %s) at 0x%s",
174 					XNUM(xntohl(head.len)),
175 					XNUM(file.size() - head_offset),
176 					XNUM(head_offset));
177 				garbage = head_offset;
178 				/* invalidate the header */
179 				qWarning("Invalidate short item @  0x%s\n",
180 					XNUM(head_offset));
181 				file.seek(head_offset);
182 				char inval = 0xcb; // 0xca +1
183 				file.write(&inval, 1);
184 				head_offset += 4;
185 				continue;
186 			}
187 		}
188 		if (!(xntohs(head.flags) & flag)) {
189 			result = 0;
190 			break;
191 		} else {
192 			head_offset += hlen;
193 		}
194 	}
195 	if (garbage != -1) {
196 		qWarning("next(): 0x%s bytes garbage skipped at 0x%s",
197 			XNUM(head_offset - garbage), XNUM(garbage));
198 	}
199 	return result;
200 }
201 
load(db_header_t * u_header)202 unsigned char *db::load(db_header_t *u_header)
203 {
204 	uint32_t size;
205 	qint64 ret;
206 	unsigned char *data;
207 
208 	if (eof())
209 		return NULL;
210 	size = xntohl(head.len) - sizeof(db_header_t);
211 	data = (unsigned char *)malloc(size);
212 	file.seek(head_offset + sizeof(db_header_t));
213 	ret = file.read((char*)data, size);
214 	if (ret == (qint64)size) {
215 		if (u_header)
216 			convert_header(u_header);
217 		return data;
218 	} else {
219 		free(data);
220 		if (ret < 0)
221 			fileIOerr("read");
222 		return NULL;
223 	}
224 }
225 
get_header(db_header_t * u_header)226 bool db::get_header(db_header_t *u_header)
227 {
228 	if (eof())
229 		return false;
230 	convert_header(u_header);
231 	return true;
232 }
233 
backup_name()234 QString db::backup_name()
235 {
236 	return file.fileName() + "_backup_" +
237 		QDateTime::currentDateTime()
238 		.toString("yyyyMMdd_hhmmss") + ".xdb";
239 }
240 
backup()241 bool db::backup()
242 {
243 	QFile this_file, new_file;
244 	QString backup = backup_name();
245 	qint64 ret, wret;
246 	char buf[BUFSIZ];
247 
248 	this_file.setFileName(file.fileName());
249 	if (!this_file.open(QIODevice::ReadOnly)) {
250 		return false;
251 	}
252 	new_file.setFileName(backup);
253 	if (!new_file.open(QIODevice::ReadWrite)) {
254 		this_file.close();
255 		return false;
256 	}
257 	while (1) {
258 		ret = this_file.read(buf, sizeof buf);
259 		if (ret <= 0)
260 			break;
261 		wret = new_file.write(buf, ret);
262 		if (wret != ret)
263 			break;
264 	}
265 	this_file.close();
266 	new_file.close();
267 	return ret == 0;
268 }
269 
intToData(uint32_t val)270 QByteArray db::intToData(uint32_t val)
271 {
272 	uint32_t v = xhtonl(val);
273 	return QByteArray((char*)&v, sizeof(uint32_t));
274 }
275 
intFromData(QByteArray & ba)276 uint32_t db::intFromData(QByteArray &ba)
277 {
278 	uint32_t ret;
279 	if ((unsigned)(ba.count()) < sizeof(uint32_t)) {
280 		throw errorEx(QObject::tr("Out of data"));
281 	}
282 	memcpy(&ret, ba.constData(), sizeof(uint32_t));
283 	ba = ba.mid(sizeof(uint32_t));
284 	return xntohl(ret);
285 }
286 
boolToData(bool val)287 QByteArray db::boolToData(bool val)
288 {
289 	char c = val ? 1 : 0;
290 	return QByteArray(&c, 1);
291 }
292 
boolFromData(QByteArray & ba)293 bool db::boolFromData(QByteArray &ba)
294 {
295 	unsigned char c;
296 	if (ba.count() < 1)
297 		throw errorEx(QObject::tr("Out of data"));
298 
299 	c = ba.constData()[0];
300 	ba = ba.mid(1);
301 	return c ? true : false;
302 }
303 
stringToData(const QString val)304 QByteArray db::stringToData(const QString val)
305 {
306 	QByteArray ba = val.toUtf8();
307 	int idx = ba.indexOf('\0');
308 	if (idx == -1)
309 		ba += '\0';
310 	else
311 		ba.truncate(idx +1);
312 	return ba;
313 }
314 
stringFromData(QByteArray & ba)315 QString db::stringFromData(QByteArray &ba)
316 {
317 	int idx = ba.indexOf('\0');
318 
319 	if (idx == -1)
320 		throw errorEx(QObject::tr("Error finding endmarker of string"));
321 
322 	QString ret = QString::fromUtf8(ba.constData(), idx);
323 	ba = ba.mid(idx+1);
324 	return ret;
325 }
326