Initial commit
[keepassx] / src / dialogs / PasswordGenDlg.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005 by Tarek Saidi                                     *
3  *   tarek@linux                                                           *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; version 2 of the License.               *
8
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21
22 #include "dialogs/PasswordGenDlg.h"
23 #include "dialogs/CollectEntropyDlg.h"
24
25 #include "random.h"
26 #include "apg/randpass.h"
27 #include "apg/pronpass.h"
28
29 bool CGenPwDialog::EntropyCollected=false;
30
31 CGenPwDialog::CGenPwDialog(QWidget* parent, bool StandAloneMode,Qt::WFlags fl)
32 : QDialog(parent,fl)
33 {
34         setupUi(this);
35         connect(ButtonGenerate, SIGNAL(clicked()), SLOT(OnGeneratePw()));
36         connect(DialogButtons, SIGNAL(rejected()), SLOT(OnCancel()));
37         connect(DialogButtons, SIGNAL(accepted()), SLOT(OnAccept()));
38         connect(tabCategory, SIGNAL(currentChanged(int)), SLOT(estimateQuality()));
39         connect(checkBox1, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
40         connect(checkBox2, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
41         connect(checkBox3, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
42         connect(checkBox4, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
43         connect(checkBox5, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
44         connect(checkBox6, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
45         connect(checkBox7, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
46         connect(Edit_chars, SIGNAL(textChanged(const QString&)), SLOT(estimateQuality()));
47         connect(checkBoxPU, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
48         connect(checkBoxPL, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
49         connect(checkBoxPN, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
50         connect(checkBoxPS, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
51         connect(Spin_Num, SIGNAL(valueChanged(int)), SLOT(estimateQuality()));
52         connect(Check_ExcludeLookAlike, SIGNAL(toggled(bool)), SLOT(estimateQuality()));
53         connect(Check_CollectEntropy, SIGNAL(stateChanged(int)), SLOT(OnCollectEntropyChanged(int)));
54         connect(ButtonChangeEchoMode, SIGNAL(clicked()), SLOT(SwapEchoMode()));
55         connect(tabCategory, SIGNAL(currentChanged(int)), SLOT(setGenerateEnabled()));
56         connect(checkBox1, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
57         connect(checkBox2, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
58         connect(checkBox3, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
59         connect(checkBox4, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
60         connect(checkBox5, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
61         connect(checkBox6, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
62         connect(checkBox7, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
63         connect(Edit_chars, SIGNAL(textChanged(const QString&)), SLOT(setGenerateEnabled()));
64         connect(checkBoxPU, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
65         connect(checkBoxPL, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
66         connect(checkBoxPN, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
67         connect(checkBoxPS, SIGNAL(toggled(bool)), SLOT(setGenerateEnabled()));
68
69         if(!StandAloneMode){
70                 AcceptButton=DialogButtons->addButton(QDialogButtonBox::Ok);
71                 AcceptButton->setEnabled(false);
72                 DialogButtons->addButton(QDialogButtonBox::Cancel);
73                 connect(Edit_dest, SIGNAL(textChanged(const QString&)), SLOT(setAcceptEnabled(const QString&)));
74         }
75         else{
76                 DialogButtons->addButton(QDialogButtonBox::Close);
77                 AcceptButton=NULL;
78         }
79         
80         Edit_chars->setValidator(new PassCharValidator(this));
81         
82         tabCategory->setCurrentIndex(config->pwGenCategory());
83         QBitArray pwGenOptions=config->pwGenOptions();
84         //Radio_1->setChecked(pwGenOptions.at(0));
85         //Radio_2->setChecked(!pwGenOptions.at(0));
86         checkBox1->setChecked(pwGenOptions.at(1));
87         checkBox2->setChecked(pwGenOptions.at(2));
88         checkBox3->setChecked(pwGenOptions.at(3));
89         checkBox4->setChecked(pwGenOptions.at(4));
90         checkBox5->setChecked(pwGenOptions.at(5));
91         checkBox6->setChecked(pwGenOptions.at(6));
92         checkBox7->setChecked(pwGenOptions.at(7));
93         Check_CollectEntropy->setChecked(pwGenOptions.at(8));
94         Check_CollectOncePerSession->setChecked(pwGenOptions.at(9));
95         //OnRadio1StateChanged(pwGenOptions.at(0));
96         //OnRadio2StateChanged(!pwGenOptions.at(0));
97         if (pwGenOptions.size()>=14){
98                 checkBoxPU->setChecked(pwGenOptions.at(10));
99                 checkBoxPL->setChecked(pwGenOptions.at(11));
100                 checkBoxPN->setChecked(pwGenOptions.at(12));
101                 checkBoxPS->setChecked(pwGenOptions.at(13));
102         }
103         else{
104                 checkBoxPU->setChecked(true);
105                 checkBoxPL->setChecked(true);
106                 checkBoxPN->setChecked(true);
107                 checkBoxPS->setChecked(false);
108         }
109         Edit_chars->setText(config->pwGenCharList());
110         Check_ExcludeLookAlike->setChecked(config->pwGenExcludeLookAlike());
111         Check_EveryGroup->setChecked(config->pwGenEveryGroup());
112         Spin_Num->setValue(config->pwGenLength());
113         adjustSize();
114         resize(size() + QSize(5, 10));
115         createBanner(&BannerPixmap,getPixmap("dice"),tr("Password Generator"),width());
116         
117         if(!config->showPasswords())
118                 SwapEchoMode();
119         else
120                 ButtonChangeEchoMode->setIcon(getIcon("pwd_show"));
121 }
122
123 CGenPwDialog::~CGenPwDialog(){
124         config->setPwGenCategory(tabCategory->currentIndex());
125         QBitArray pwGenOptions(14);
126         //pwGenOptions.setBit(0,Radio_1->isChecked());
127         pwGenOptions.setBit(1,checkBox1->isChecked());
128         pwGenOptions.setBit(2,checkBox2->isChecked());
129         pwGenOptions.setBit(3,checkBox3->isChecked());
130         pwGenOptions.setBit(4,checkBox4->isChecked());
131         pwGenOptions.setBit(5,checkBox5->isChecked());
132         pwGenOptions.setBit(6,checkBox6->isChecked());
133         pwGenOptions.setBit(7,checkBox7->isChecked());
134         pwGenOptions.setBit(8,Check_CollectEntropy->isChecked());
135         pwGenOptions.setBit(9,Check_CollectOncePerSession->isChecked());
136         pwGenOptions.setBit(10,checkBoxPU->isChecked());
137         pwGenOptions.setBit(11,checkBoxPL->isChecked());
138         pwGenOptions.setBit(12,checkBoxPN->isChecked());
139         pwGenOptions.setBit(13,checkBoxPS->isChecked());
140         config->setPwGenOptions(pwGenOptions);
141         config->setPwGenCharList(Edit_chars->text());
142         config->setPwGenExcludeLookAlike(Check_ExcludeLookAlike->isChecked());
143         config->setPwGenEveryGroup(Check_EveryGroup->isChecked());
144         config->setPwGenLength(Spin_Num->value());
145 }
146
147 void CGenPwDialog::paintEvent(QPaintEvent *event){
148         QDialog::paintEvent(event);
149         QPainter painter(this);
150         painter.setClipRegion(event->region());
151         painter.drawPixmap(QPoint(0,0),BannerPixmap);
152 }
153
154 void CGenPwDialog::OnGeneratePw()
155 {
156         if(Check_CollectEntropy->isChecked()){
157                 if((Check_CollectOncePerSession->isChecked() && !EntropyCollected) || !Check_CollectOncePerSession->isChecked()){
158                         CollectEntropyDlg dlg(this);
159                         dlg.exec();
160                         EntropyCollected=true;
161                 }
162         }
163         
164         int length = Spin_Num->value();
165         QString password;
166         
167         if (tabCategory->currentIndex() == 1)
168         {
169                 unsigned int mode = 0;
170                 if (checkBoxPU->isChecked())
171                         mode |= S_CL;
172                 if (checkBoxPL->isChecked())
173                         mode |= S_SL;
174                 if (checkBoxPN->isChecked())
175                         mode |= S_NB;
176                 if (checkBoxPS->isChecked())
177                         mode |= S_SS;
178                 
179                 char* buffer = new char[length+1];
180                 char* hyphenated_word = new char[length*18+1];
181                 gen_pron_pass(buffer, hyphenated_word, length, length, mode);
182                 password = buffer;
183                 delete[] hyphenated_word;
184                 delete[] buffer;
185         }
186         else{
187                 password = generatePasswordInternal(length);
188         }
189
190         Edit_dest->setText(password);
191 }
192
193 void CGenPwDialog::estimateQuality(){
194         int num = 0;
195         int index = tabCategory->currentIndex();
196         if (index == 0) {
197                 if (checkBox1->isChecked()) {
198                         num+=26;
199                         if (Check_ExcludeLookAlike->isChecked())
200                                 num -= 2;
201                 }
202                 if (checkBox2->isChecked()) {
203                         num+=26;
204                         if (Check_ExcludeLookAlike->isChecked())
205                                 num -= 1;
206                 }
207                 if (checkBox3->isChecked()) {
208                         num+=10;
209                         if (Check_ExcludeLookAlike->isChecked())
210                                 num -= 2;
211                 }
212                 if (checkBox4->isChecked()) {
213                         num+=32;
214                         if (Check_ExcludeLookAlike->isChecked())
215                                 num -= 1;
216                 }
217                 if (checkBox5->isChecked())
218                         num++;
219                 if (checkBox6->isChecked())
220                         num++;
221                 if (checkBox7->isChecked())
222                         num++;
223         }
224         else if (index == 1) {
225                 if (checkBoxPU->isChecked())
226                         num+=26;
227                 if (checkBoxPL->isChecked())
228                         num+=26;
229                 if (checkBoxPN->isChecked())
230                         num+=10;
231                 if (checkBoxPS->isChecked())
232                         num+=32;
233         }
234         else {
235                 num=Edit_chars->text().length();
236         }
237
238         float bits = 0;
239         if (num)
240                 bits = log((float)num) / log(2.0f);
241         bits = bits * ((float)Spin_Num->value());
242         int bitsNum = (int) (bits+0.5);
243         
244         Progress_Quali->setFormat(tr("%1 Bits").arg(bitsNum));
245         Progress_Quali->update();
246         Progress_Quali->setValue((bitsNum > 128) ? 128 : bitsNum);
247 }
248
249 void CGenPwDialog::OnAccept()
250 {
251         done(1);
252 }
253
254 void CGenPwDialog::OnCancel()
255 {
256         done(0);
257 }
258
259 void CGenPwDialog::OnCollectEntropyChanged(int state){
260         if(state==Qt::Checked)
261                 Check_CollectOncePerSession->setDisabled(false);
262         else
263                 Check_CollectOncePerSession->setDisabled(true);
264
265 }
266
267 void CGenPwDialog::SwapEchoMode(){
268         if(Edit_dest->echoMode()==QLineEdit::Normal){
269                 Edit_dest->setEchoMode(QLineEdit::Password);
270                 ButtonChangeEchoMode->setIcon(getIcon("pwd_hide"));
271         }
272         else{
273                 Edit_dest->setEchoMode(QLineEdit::Normal);
274                 ButtonChangeEchoMode->setIcon(getIcon("pwd_show"));
275         }
276 }
277
278 void CGenPwDialog::AddToAssoctable(QList<QChar>& table,int start,int end,int& pos){
279         for (int i=start;i<=end;i++){
280                 if (Check_ExcludeLookAlike->isChecked()){
281                         switch (i){
282                                 case 48:  // 0
283                                 case 79:  // O
284                                 case 49:  // 1
285                                 case 73:  // I
286                                 case 108: // l
287                                 case 124: // |
288                                         continue;
289                         }
290                 }
291                 table.append(QChar(i));
292                 pos++;
293         }
294 }
295
296 CGenPwDialog::PwGroup CGenPwDialog::AddToAssoctableGroup(QList<QChar>& table,int start,int end,int& pos){
297         PwGroup group;
298         group.start = pos;
299         AddToAssoctable(table,start,end,pos);
300         group.end = pos-1;
301         return group;
302 }
303
304 QString CGenPwDialog::generatePasswordInternal(int length){
305         /*-------------------------------------------------------
306              ASCII
307           -------------------------------------------------------
308           "A...Z" 65...90
309           "a...z" 97...122
310           "0...9" 48...57
311           Special Characters 33...47; 58...64; 91...96; 123...126
312           "-" 45
313           "_" 95
314           -------------------------------------------------------
315         */
316
317         int num=0;
318         QList<QChar> assoctable;
319         int groups=0;
320         bool ensureEveryGroup = false;
321         QList<PwGroup> groupTable;
322         
323         if (tabCategory->currentIndex() == 0) {
324                 if (Check_EveryGroup->isChecked()){
325                         if (checkBox1->isChecked()) groups++;
326                         if (checkBox2->isChecked()) groups++;
327                         if (checkBox3->isChecked()) groups++;
328                         if (checkBox4->isChecked()) groups++;
329                         if (checkBox5->isChecked()) groups++;
330                         if (checkBox6->isChecked()) groups++;
331                         if (checkBox7->isChecked()) groups++;
332                         if (groups<=length)
333                                 ensureEveryGroup = true;
334                 }
335                 
336                 if(checkBox1->isChecked()){
337                         if (ensureEveryGroup) groupTable.append(AddToAssoctableGroup(assoctable,65,90,num));
338                         else AddToAssoctable(assoctable,65,90,num);
339                 }
340                 if(checkBox2->isChecked()){
341                         if (ensureEveryGroup) groupTable.append(AddToAssoctableGroup(assoctable,97,122,num));
342                         else AddToAssoctable(assoctable,97,122,num);
343                 }
344                 if(checkBox3->isChecked()){
345                         if (ensureEveryGroup) groupTable.append(AddToAssoctableGroup(assoctable,48,57,num));
346                         else AddToAssoctable(assoctable,48,57,num);
347                 }
348                 if(checkBox4->isChecked()){
349                         PwGroup group;
350                         group.start = num;
351                         AddToAssoctable(assoctable,33,44,num);
352                         AddToAssoctable(assoctable,46,47,num);
353                         AddToAssoctable(assoctable,58,64,num);
354                         AddToAssoctable(assoctable,91,94,num);
355                         AddToAssoctable(assoctable,96,96,num);
356                         AddToAssoctable(assoctable,123,126,num);
357                         if (ensureEveryGroup){
358                                 group.end = num-1;
359                                 groupTable.append(group);
360                         }
361                         
362                 }
363                 if(checkBox5->isChecked()){
364                         if (ensureEveryGroup) groupTable.append(AddToAssoctableGroup(assoctable,32,32,num));
365                         else AddToAssoctable(assoctable,32,32,num);
366                 }
367                 if(checkBox6->isChecked()){
368                         if (ensureEveryGroup) groupTable.append(AddToAssoctableGroup(assoctable,45,45,num));
369                         else AddToAssoctable(assoctable,45,45,num);
370                 }
371                 if(checkBox7->isChecked()){
372                         if (ensureEveryGroup) groupTable.append(AddToAssoctableGroup(assoctable,95,95,num));
373                         else AddToAssoctable(assoctable,95,95,num);
374                 }
375         }
376         else {
377                 QString str=Edit_chars->text();
378                 for(int i=0;i<str.length();i++){
379                         assoctable.append(str[i]);
380                         num++;
381                 }
382         }
383         
384         QString password(length, '\0');
385         
386         if (ensureEveryGroup){
387                 QList<int> charPos;
388                 for (int i=0; i<length; i++)
389                         charPos.append(i);
390                 
391                 for (int i=0; i<groups; i++){
392                         int posIndex = randintRange(0, charPos.count()-1);
393                         int pos = charPos[posIndex];
394                         charPos.removeAt(posIndex);
395                         password[pos] = assoctable[randintRange(groupTable[i].start, groupTable[i].end)];
396                 }
397                 
398                 for (int i=groups; i<length; i++){
399                         int posIndex = randintRange(0, charPos.count()-1);
400                         int pos = charPos[posIndex];
401                         charPos.removeAt(posIndex);
402                         password[pos] = assoctable[randint(num)];
403                 }
404         }
405         else{
406                 for (int i=0; i<length; i++)
407                         password[i] = assoctable[randint(num)];
408         }
409         
410         return password;
411 }
412
413 void CGenPwDialog::setGenerateEnabled(){
414         bool enable;
415         int index = tabCategory->currentIndex();
416         
417         if (index == 0) {
418                 enable = checkBox1->isChecked() || checkBox2->isChecked() || checkBox3->isChecked() ||
419                                  checkBox4->isChecked() || checkBox5->isChecked() || checkBox6->isChecked() ||
420                                  checkBox7->isChecked();
421         }
422         else if (index == 1) {
423                 enable = checkBoxPU->isChecked() || checkBoxPL->isChecked() ||
424                                  checkBoxPN->isChecked() || checkBoxPS->isChecked();
425         }
426         else {
427                 enable = !Edit_chars->text().isEmpty();
428         }
429         
430         ButtonGenerate->setEnabled(enable);
431 }
432
433 void CGenPwDialog::setAcceptEnabled(const QString& str){
434         AcceptButton->setEnabled(!str.isEmpty());
435 }
436
437 PassCharValidator::PassCharValidator(QObject* parent) : QValidator(parent) {
438 }
439
440 QValidator::State PassCharValidator::validate(QString& input, int& pos) const {
441         Q_UNUSED(pos);
442         QSet<QChar> chars;
443         
444         for (int i=0; i<input.size(); i++) {
445                 if (chars.contains(input[i]))
446                         return QValidator::Invalid;
447                 else
448                         chars.insert(input[i]);
449         }
450         
451         return QValidator::Acceptable;
452 }