Improve password input handling

Unify password results
Retry on password verification error
Honor the users wish to exit
This commit is contained in:
Christian Hohnstaedt 2020-02-23 15:50:26 +01:00
parent 30824964a2
commit a90d7b5b0d
21 changed files with 202 additions and 136 deletions

View File

@ -85,7 +85,7 @@ bool database_model::checkForOldDbFormat(const QString &dbfile) const
return !memcmp(head, magic, sizeof head);
}
int database_model::verifyOldDbPass(const QString &dbname) const
enum open_result database_model::verifyOldDbPass(const QString &dbname) const
{
// look for the password
QString passhash;
@ -101,11 +101,12 @@ int database_model::verifyOldDbPass(const QString &dbname) const
return initPass(dbname, passhash);
}
}
return 2;
return open_abort;
}
QString database_model::checkPre2Xdatabase() const
{
enum open_result result;
if (!checkForOldDbFormat(dbName))
return QString();
@ -115,14 +116,16 @@ QString database_model::checkPre2Xdatabase() const
newname += "_backup_" + QDateTime::currentDateTime()
.toString("yyyyMMdd_hhmmss") + ".xdb";
if (!XCA_OKCANCEL(tr("Legacy database format detected. Creating a backup copy called: '%1' and converting the database to the new format").arg(newname))) {
throw 1;
throw open_abort;
}
if (verifyOldDbPass(dbName) != 1)
throw 1;
result = verifyOldDbPass(dbName);
if (result != pw_ok)
throw result;
if (!QFile::rename(dbName, newname)) {
XCA_WARN(tr("Failed to rename the database file, because the target already exists"));
throw 1;
throw open_abort;
}
return newname;
}
@ -227,7 +230,7 @@ next:
database_model::database_model(const QString &name, const Passwd &pass)
{
int ret = 2;
enum open_result result;
QSqlError err;
QString oldDbFile;
@ -238,7 +241,7 @@ database_model::database_model(const QString &name, const Passwd &pass)
dbName = get_default_db();
if (dbName.isEmpty())
throw errorEx(1);
throw open_abort;
qDebug("Opening database: %s", QString2filename(dbName));
@ -257,24 +260,21 @@ database_model::database_model(const QString &name, const Passwd &pass)
DbMap params = splitRemoteDbName(dbName);
pass_info p(XCA_TITLE, tr("Please enter the password to access the database server %2 as user '%1'.")
.arg(params["user"]).arg(params["host"]));
if (PwDialog::execute(&p, &passwd) != 1)
throw errorEx(1);
result = PwDialog::execute(&p, &passwd);
if (result != pw_ok)
throw result;
}
} while (1);
Entropy::seed_rng();
initSqlDB();
if (dbName.isEmpty()) {
/* Error already printed */
throw errorEx(1);
}
if (oldDbFile.isEmpty()) {
ret = initPass(dbName, Settings["pwhash"]);
if (ret == 2)
throw errorEx(ret);
if (ret == 0 && Settings["pwhash"].empty())
throw errorEx(2);
result = initPass(dbName, Settings["pwhash"]);
if (result == pw_exit)
throw pw_exit;
if (result != pw_ok && Settings["pwhash"].empty())
throw open_abort;
}
/* Assure initialisation order:
* keys first, followed by x509[req], and crls last.
@ -485,7 +485,7 @@ void database_model::openRemoteDatabase(const QString &connName,
if (e.isValid() || !db.isOpen()) {
XSqlQuery::clearTablePrefix();
db.close();
throw errorEx(e.text());
throw errorEx(e);
}
/* This is MySQL specific. Execute it always, because
* dbType() could return "ODBC" but connect to MariaDB
@ -520,7 +520,7 @@ void database_model::openLocalDatabase(const QString &connName,
QSqlError e = db.lastError();
if (e.isValid()) {
db.close();
throw errorEx(e.text());
throw errorEx(e);
}
}
@ -573,10 +573,10 @@ static void pwhash_upgrade()
}
}
int database_model::initPass(const QString &dbName, const QString &passhash) const
enum open_result database_model::initPass(const QString &dbName, const QString &passhash) const
{
QString salt, pass;
int ret;
enum open_result result = pw_cancel;
pass_info p(tr("New Password"), tr("Please enter a password, "
"that will be used to encrypt your private keys "
@ -585,33 +585,31 @@ int database_model::initPass(const QString &dbName, const QString &passhash) con
pki_evp::passHash = passhash;
if (pki_evp::passHash.isEmpty()) {
ret = PwDialog::execute(&p, &pki_evp::passwd, true, true);
if (ret != 1)
return ret;
result = PwDialog::execute(&p, &pki_evp::passwd, true, true);
if (result != pw_ok)
return result;
salt = Entropy::makeSalt();
pki_evp::passHash =pki_evp::sha512passwT(pki_evp::passwd,salt);
Settings["pwhash"] = pki_evp::passHash;
} else {
pwhash_upgrade();
ret = 0;
while (pki_evp::sha512passwT(pki_evp::passwd, pki_evp::passHash)
!= pki_evp::passHash)
{
if (ret)
XCA_WARN(
tr("Password verify error, please try again"));
if (result == pw_ok)
XCA_PASSWD_ERROR();
p.setTitle(tr("Password"));
p.setDescription(tr("Please enter the password for unlocking the database:\n%1").arg(compressFilename(dbName)));
ret = PwDialog::execute(&p, &pki_evp::passwd,
result = PwDialog::execute(&p, &pki_evp::passwd,
false, true);
if (ret != 1) {
if (result != pw_ok) {
pki_evp::passwd = QByteArray();
return ret;
return result;
}
pwhash_upgrade();
}
}
if (pki_evp::passwd.isNull())
pki_evp::passwd = "";
return 1;
return pw_ok;
}

View File

@ -30,11 +30,11 @@ class database_model: public QObject
QSqlError initSqlDB();
QString dbName;
bool checkForOldDbFormat(const QString &dbfile) const;
int verifyOldDbPass(const QString &dbname) const;
enum open_result verifyOldDbPass(const QString &dbname) const;
void importOldDatabase(const QString &dbfile);
QString get_default_db() const;
QString checkPre2Xdatabase() const;
int initPass(const QString &dbName,
enum open_result initPass(const QString &dbName,
const QString &passhash) const;
public:

View File

@ -10,38 +10,32 @@
#include <QString>
#include <QObject>
#include <QSqlError>
#include "base.h"
#define E_PASSWD 1
enum open_result {
pw_cancel,
pw_ok,
pw_exit,
open_abort
};
class errorEx
{
private:
protected:
QString msg;
public:
int info;
errorEx(QString txt = "", QString className = "", int inf = 0)
errorEx(QString txt = "", QString className = "")
{
msg = txt;
if (!className.isEmpty())
msg += " (" + className + ")";
info = inf;
}
errorEx(int inf)
errorEx(const QSqlError &e)
{
info = inf;
msg.clear();
}
errorEx(const errorEx &e)
{
msg = e.msg;
info = e.info;
}
errorEx &operator = (const errorEx &e)
{
msg = e.msg;
info = e.info;
return *this;
msg = e.text();
}
void appendString(QString s)
{

View File

@ -25,7 +25,8 @@
#define COL_CYAN "\x1b[0;36m"
#define COL_BLUE "\x1b[0;94m"
#define COL_LRED "\x1b[0;92m"
#define COL_GREEN "\x1b[0;92m"
#define COL_LRED "\x1b[0;91m"
#define COL_YELL "\x1b[0;33m"
#define COL_RED "\x1b[0;31m"
#define COL_RESET "\x1b[0m"

View File

@ -7,7 +7,7 @@
#include <signal.h>
#include <QClipboard>
//#include <QClipboard>
#include <QDir>
#include <QDebug>
//#include <openssl/rand.h>
@ -105,14 +105,16 @@ QCoreApplication *createApplication(int &argc, char *argv[])
static void cmd_version(FILE *fp)
{
fprintf(fp, XCA_TITLE " Version %s\n", version_str(false));
fprintf(fp, XCA_TITLE "\nVersion %s\n", version_str(false));
}
const char *xca_name = "xca";
static void cmd_help(int exitcode = EXIT_SUCCESS, const char *msg = NULL)
{
FILE *fp = exitcode == EXIT_SUCCESS ? stdout : stderr;
cmd_version(fp);
fprintf(fp, "\nUsage %s <options> <file-to-import> ...\n\n", xca_name);
fputs(CCHAR(arguments::help()), fp);
if (msg)
@ -192,8 +194,8 @@ static database_model* read_cmdline(int argc, char *argv[])
int main(int argc, char *argv[])
{
int ret = 0;
QDir d;
if (argc > 0)
xca_name = argv[0];
#if defined(Q_OS_WIN32)
SetUnhandledExceptionFilter(w32_segfault);
@ -201,7 +203,7 @@ int main(int argc, char *argv[])
signal(SIGSEGV, segv_handler_gui);
#endif
d.mkpath(getUserSettingsDir());
QDir().mkpath(getUserSettingsDir());
#if QT_VERSION < 0x050000
qInstallMsgHandler(myMsgOutput);
@ -216,8 +218,8 @@ int main(int argc, char *argv[])
XcaApplication *gui = qobject_cast<XcaApplication*>(core);
try {
#warning FIXME cmdline
database_model *models = read_cmdline(argc, argv);
TRACE
if (gui) {
OpenDb::checkSqLite();
mainwin = new MainWindow(models);
@ -228,10 +230,16 @@ int main(int argc, char *argv[])
gui->exec();
}
} catch (errorEx &ex) {
TRACE
XCA_ERROR(ex);
} catch (enum open_result r) {
XCA_ERROR(QObject::tr("DB Open failed: %1").arg(r));
TRACE
}
delete mainwin;
TRACE
delete gui;
TRACE
return ret;
return EXIT_SUCCESS;
}

View File

@ -14,6 +14,7 @@ pass_info::pass_info(const QString &t, const QString &d, QWidget *w)
widget = w;
type = tr("Password");
pixmap = QString(":keyImg");
result = pw_ok;
}
void pass_info::setPin()

View File

@ -12,25 +12,29 @@
#include <QObject>
#include <QApplication>
#include "lib/exception.h"
class QWidget;
class pass_info: public QObject
{
Q_OBJECT
Q_OBJECT
private:
QString title;
QString description;
QWidget *widget;
QString type;
QString pixmap;
enum open_result result;
public:
pass_info(const QString &t, const QString &d, QWidget *w = NULL);
QString getTitle()
QString getTitle() const
{
return title;
}
QString getDescription()
QString getDescription() const
{
return description;
}
@ -40,14 +44,18 @@ class pass_info: public QObject
widget = qApp->activeWindow();
return widget;
}
QString getType()
QString getType() const
{
return type;
}
QString getImage()
QString getImage() const
{
return pixmap;
}
enum open_result getResult() const
{
return result;
}
void setTitle(QString t)
{
title = t;
@ -60,6 +68,10 @@ class pass_info: public QObject
{
widget = w;
}
void setResult(enum open_result r)
{
result = r;
}
void setPin();
};

View File

@ -297,14 +297,15 @@ bool pkcs11::selectToken(slotid *slot, QWidget *w)
for (int i = 0; i < p11_slots.count(); i++) {
try {
tkInfo info = tokenInfo(p11_slots[i]);
tkInfo info;
CK_RV rv = tokenInfo(p11_slots[i], &info);
if (rv == CKR_TOKEN_NOT_PRESENT)
continue;
slotsWithToken.append(i);
slotnames << QString("%1 (#%2)").
arg(info.label()).arg(info.serial());
} catch (errorEx &e) {
if (e.info != CKR_TOKEN_NOT_PRESENT) {
XCA_WARN(QString("Error: %1").arg(e.getString()));
}
XCA_WARN(QString("Error: %1").arg(e.getString()));
}
}
switch (slotnames.count()) {
@ -423,15 +424,25 @@ void pkcs11::initToken(slotid slot, unsigned char *pin, int pinlen,
}
tkInfo pkcs11::tokenInfo(slotid slot)
{
tkInfo ti;
CK_RV rv = tokenInfo(slot, &ti);
if (rv != CKR_OK) {
pk11error(slot, "C_GetTokenInfo", rv);
}
return ti;
}
CK_RV pkcs11::tokenInfo(slotid slot, tkInfo *tkinfo)
{
CK_TOKEN_INFO token_info;
CK_RV rv;
CALL_P11_C(slot.lib, C_GetTokenInfo, slot.id, &token_info);
if (rv != CKR_OK) {
pk11error(slot, "C_GetTokenInfo", rv);
}
return tkInfo(&token_info);
if (rv == CKR_OK)
tkinfo->set(&token_info);
return rv;
}
void pkcs11::loadAttribute(pk11_attribute &attribute, CK_OBJECT_HANDLE object)

View File

@ -42,6 +42,10 @@ class tkInfo
private:
CK_TOKEN_INFO token_info;
public:
tkInfo()
{
memset(&token_info, 0, sizeof token_info);
}
tkInfo(const CK_TOKEN_INFO *ti)
{
set(ti);
@ -143,6 +147,7 @@ class pkcs11
return libs;
}
CK_RV tokenInfo(slotid slot, tkInfo *tkinfo);
tkInfo tokenInfo(slotid slot);
tkInfo tokenInfo()
{

View File

@ -319,6 +319,6 @@ void pk11error(slotid slot, const QString &func, int rv)
WAITCURSOR_END
errorEx err(QObject::tr("PKCS#11 function '%1' failed: %2\nIn library %3\n%4").
arg(func).arg(pk11errorString(rv)).arg(slot.lib->filename()).
arg(slot.lib->driverInfo()), "", rv);
arg(slot.lib->driverInfo()));
throw err;
}

View File

@ -350,7 +350,7 @@ bool pki_base::compare(const pki_base *ref) const
}
/* Unsigned 32 bit integer */
unsigned pki_base::hash(QByteArray ba)
unsigned pki_base::hash(const QByteArray &ba)
{
unsigned char md[EVP_MAX_MD_SIZE];

View File

@ -10,7 +10,7 @@
#include <openssl/err.h>
#include <QString>
#include <QListView>
//#include <QListView>
#include "asn1time.h"
#include "pkcs11_lib.h"
#include "db.h"
@ -51,7 +51,7 @@ class pki_base : public QObject
public: /* static */
static QRegExp limitPattern;
static QString rmslashdot(const QString &fname);
static unsigned hash(QByteArray ba);
static unsigned hash(const QByteArray &ba);
static bool pem_comment;
protected:

View File

@ -12,6 +12,7 @@
#include "db.h"
#include "entropy.h"
#include "widgets/PwDialog.h"
#include "widgets/XcaWarning.h"
#include <openssl/rand.h>
#include <openssl/evp.h>
@ -228,16 +229,16 @@ pki_evp::pki_evp(EVP_PKEY *pkey)
set_EVP_PKEY(pkey);
}
void pki_evp::openssl_pw_error(QString fname)
bool pki_evp::openssl_pw_error() const
{
switch (ERR_peek_error() & 0xff000fff) {
case ERR_PACK(ERR_LIB_PEM, 0, PEM_R_BAD_DECRYPT):
case ERR_PACK(ERR_LIB_PEM, 0, PEM_R_BAD_PASSWORD_READ):
case ERR_PACK(ERR_LIB_EVP, 0, EVP_R_BAD_DECRYPT):
pki_ign_openssl_error();
throw errorEx(tr("Failed to decrypt the key (bad password) %1")
.arg(fname), getClassName(), E_PASSWD);
return true;
}
return false;
}
void pki_evp::fromPEMbyteArray(const QByteArray &ba, const QString &name)
@ -245,15 +246,24 @@ void pki_evp::fromPEMbyteArray(const QByteArray &ba, const QString &name)
BIO *bio = BIO_from_QByteArray(ba);
EVP_PKEY *pkey;
pass_info p(XCA_TITLE,
tr("Please enter the password to decrypt the private key.") +
" " + name);
pkey = PEM_read_bio_PrivateKey(bio, NULL, PwDialog::pwCallback, &p);
openssl_pw_error(name);
tr("Please enter the password to decrypt the private key %1.")
.arg(name));
do {
pkey = PEM_read_bio_PrivateKey(bio, NULL,
PwDialog::pwCallback, &p);
if (openssl_pw_error())
XCA_PASSWD_ERROR();
if (p.getResult() != pw_ok)
throw p.getResult();
if (pki_ign_openssl_error())
break;
} while (!pkey);
if (!pkey) {
pki_ign_openssl_error();
BIO_free(bio);
bio = BIO_from_QByteArray(ba);
pkey = PEM_read_bio_PUBKEY(bio, NULL, PwDialog::pwCallback, &p);
pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, 0);
}
BIO_free(bio);
pki_openssl_error();
@ -326,8 +336,17 @@ void pki_evp::fload(const QString &fname)
pki_ign_openssl_error();
XFile file(fname);
file.open_read();
EVP_PKEY *pkey = PEM_read_PrivateKey(file.fp(), NULL, cb, &p);
openssl_pw_error(fname);
EVP_PKEY *pkey;
do {
pkey = PEM_read_PrivateKey(file.fp(), NULL, cb, &p);
if (openssl_pw_error())
XCA_PASSWD_ERROR();
if (p.getResult() != pw_ok)
throw p.getResult();
if (pki_ign_openssl_error())
break;
file.retry_read();
} while (!pkey);
if (!pkey) {
pki_ign_openssl_error();
file.retry_read();

View File

@ -34,7 +34,7 @@ class pki_evp: public pki_key
void set_EVP_PKEY(EVP_PKEY *pkey, QString name = QString());
protected:
void openssl_pw_error(QString fname);
bool openssl_pw_error() const;
public:
static QString passHash;
static Passwd passwd;

View File

@ -153,10 +153,10 @@ void pki_multi::probeAnything(const QString &fname)
break;
}
} catch (errorEx &err) {
if (err.info == E_PASSWD) {
XCA_ERROR(err);
XCA_ERROR(err);
} catch (enum open_result r) {
if (r == pw_cancel)
break;
}
}
}
if (count() == old_count && !fname.isEmpty())

View File

@ -11,6 +11,7 @@
#include "exception.h"
#include "func.h"
#include "widgets/PwDialog.h"
#include "widgets/XcaWarning.h"
#include <openssl/err.h>
#include <QMessageBox>
@ -42,14 +43,18 @@ pki_pkcs12::pki_pkcs12(const QString &fname)
if (pki_ign_openssl_error()) {
if (pkcs12)
PKCS12_free(pkcs12);
throw errorEx(tr("Unable to load the PKCS#12 (pfx) file %1.").arg(fname));
throw errorEx(tr("Unable to load the PKCS#12 (pfx) file %1.")
.arg(fname));
}
if (PKCS12_verify_mac(pkcs12, "", 0) || PKCS12_verify_mac(pkcs12, NULL, 0))
pass.clear();
else if (PwDialog::execute(&p, &pass) != 1) {
/* cancel pressed */
PKCS12_free(pkcs12);
throw errorEx("","", E_PASSWD);
while (!PKCS12_verify_mac(pkcs12, pass.constData(), 0)) {
if (pass.size() > 0)
XCA_PASSWD_ERROR();
enum open_result result = PwDialog::execute(&p, &pass);
if (result != pw_ok) {
/* cancel pressed */
PKCS12_free(pkcs12);
throw result;
}
}
PKCS12_parse(pkcs12, pass.constData(), &mykey, &mycert, &certstack);
int error = ERR_peek_error();
@ -58,7 +63,7 @@ pki_pkcs12::pki_pkcs12(const QString &fname)
PKCS12_free(pkcs12);
throw errorEx(getClassName(),
tr("The supplied password was wrong (%1)")
.arg(ERR_reason_error_string(error)), E_PASSWD);
.arg(ERR_reason_error_string(error)));
}
pki_ign_openssl_error();
if (mycert) {

View File

@ -141,9 +141,10 @@ MainWindow::MainWindow(database_model *m)
dhgenBar->setMaximum(0);
models = NULL;
int ret = m ? init_database(m) : init_database(QString());
if (ret == 2)
throw errorEx(2);
enum open_result result = m ? init_database(m) :
init_database(QString());
if (result == pw_exit)
throw pw_exit;
}
void MainWindow::dropEvent(QDropEvent *event)
@ -603,26 +604,28 @@ void MainWindow::openRemoteSqlDB()
init_database(descriptor, pass);
}
int MainWindow::init_database(const QString &name, const Passwd &pass)
enum open_result MainWindow::init_database(const QString &name,
const Passwd &pass)
{
try {
close_database();
TRACE
return init_database(new database_model(name, pass));
} catch (errorEx &err) {
if (err.info == 0 && !err.isEmpty()) {
XCA_ERROR(err);
return 1;
} else {
return err.info;
}
TRACE
XCA_ERROR(err);
return open_abort;
} catch (enum open_result r) {
TRACE
return r;
}
return 1;
return pw_ok;
}
int MainWindow::init_database(database_model *m)
enum open_result MainWindow::init_database(database_model *m)
{
if (!m)
return 1;
return open_abort;
models = m;
setItemEnabled(true);
m->restart_timer();
@ -664,7 +667,8 @@ int MainWindow::init_database(database_model *m)
certs, SLOT(newCert(pki_temp *)) );
connect(tempView, SIGNAL(newReq(pki_temp *)),
reqs, SLOT(newItem(pki_temp *)) );
return 0;
return pw_ok;
}
void MainWindow::set_geometry(QString geo)

View File

@ -125,9 +125,9 @@ class MainWindow: public QMainWindow, public Ui::MainWindow
void initResolver();
public slots:
int init_database(const QString &dbName,
enum open_result init_database(const QString &dbName,
const Passwd &pass = Passwd());
int init_database(database_model *m);
enum open_result init_database(database_model *m);
void new_database();
void load_database();
void close_database();

View File

@ -5,15 +5,15 @@
* All rights reserved.
*/
#include "PwDialog.h"
#include "lib/func.h"
#include "lib/base.h"
#include "lib/Passwd.h"
#include "widgets/MainWindow.h"
#include "lib/exception.h"
#include "XcaWarning.h"
#include "PwDialog.h"
#include <QLabel>
#include <QMessageBox>
#include <QApplication>
//#include <QApplication>
static int hex2bin(QString &x, Passwd *final)
{
@ -34,34 +34,38 @@ static int hex2bin(QString &x, Passwd *final)
return len;
}
int PwDialog::execute(pass_info *p, Passwd *passwd, bool write, bool abort)
enum open_result PwDialog::execute(pass_info *p, Passwd *passwd,
bool write, bool abort)
{
if (IS_GUI_APP) {
PwDialog *dlg = new PwDialog(p, write);
if (abort)
dlg->addAbortButton();
int ret = dlg->exec();
enum open_result result = (enum open_result)dlg->exec();
*passwd = dlg->getPass();
delete dlg;
return ret;
if (result == pw_exit)
throw pw_exit;
return result;
}
printf(COL_CYAN "%s" COL_LRED "\n%s:" COL_RESET,
CCHAR(p->getDescription()), CCHAR(tr("Password")));
*passwd = readPass();
return 1;
return pw_ok;
}
int PwDialog::pwCallback(char *buf, int size, int rwflag, void *userdata)
{
Passwd passwd;
enum open_result result;
pass_info *p = static_cast<pass_info *>(userdata);
int ret = PwDialog::execute(p, &passwd, rwflag, false);
result = PwDialog::execute(p, &passwd, rwflag, false);
size = MIN(size, passwd.size());
memcpy(buf, passwd.constData(), size);
return ret == 1 ? size : 0;
p->setResult(result);
return result == pw_ok ? size : 0;
}
PwDialog::PwDialog(pass_info *p, bool write)
@ -117,20 +121,23 @@ void PwDialog::accept()
void PwDialog::buttonPress(QAbstractButton *but)
{
switch (buttonBox->standardButton(but)) {
case QDialogButtonBox::Ok:
qDebug() << "buttonBox->standardButton(but)" << buttonBox->buttonRole(but) << QDialogButtonBox::DestructiveRole;
switch (buttonBox->buttonRole(but)) {
case QDialogButtonBox::AcceptRole:
accept();
break;
case QDialogButtonBox::Cancel:
case QDialogButtonBox::RejectRole:
reject();
break;
case QDialogButtonBox::Abort:
case QDialogButtonBox::ResetRole:
done(pw_exit);
break;
default:
done(2);
break;
}
}
void PwDialog::addAbortButton()
{
buttonBox->addButton(tr("E&xit"), QDialogButtonBox::ResetRole);
buttonBox->addButton(tr("Exit"), QDialogButtonBox::ResetRole);
}

View File

@ -31,7 +31,7 @@ class PwDialog: public QDialog, public Ui::PwDialog
void addAbortButton();
void setRW(bool write);
static int execute(pass_info *p, Passwd *passwd,
static enum open_result execute(pass_info *p, Passwd *passwd,
bool write = false, bool abort = false);
static int pwCallback(char *buf, int size, int rwflag, void *userdata);

View File

@ -20,6 +20,7 @@
#define XCA_OKCANCEL(msg) xcaWarning::okcancel(msg)
#define XCA_ERROR(err) xcaWarning::error(err)
#define XCA_SQLERROR(err) xcaWarning::sqlerror(err)
#define XCA_PASSWD_ERROR() XCA_WARN(QObject::tr("Password verify error, please try again"))
class xcaWarning: public QObject
{