📄 keyedit.c
字号:
/* -*- c-file-style: "java"; -*- * * $Header: /cvsroot/gnukeyring/keyring/keyedit.c,v 1.97 2006/01/13 14:09:04 hoenicke Exp $ * * Keyring -- store passwords securely on a handheld * Copyright (C) 1999, 2000, 2001 Martin Pool <mbp@users.sourceforge.net> * Copyright (C) 2001-2005 Jochen Hoenicke <hoenicke@users.sourceforge.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */#include "includes.h"/* * TODO: Newline in single-line fields should move down one, perhaps? * Really this is only useful on POSE -- on the real system, people * can just use the next-field character. *//* This keeps an unpacked version of the record currently being * edited. They're periodically sync'd up; e.g. just before saving. * * It's a bit unfortunate to keep it in a global like this, but the * problem is that the date isn't stored by the GUI control, so we * need somewhere to keep it. */static UnpackedKeyType *gRecord;static GUI_SECTION void KeyEdit_Save(void);static GUI_SECTION void KeyEdit_UpdateScrollbar(void);static GUI_SECTION Boolean KeyEdit_IsDirty(void);static GUI_SECTION Boolean KeyEdit_IsEmpty(void);static GUI_SECTION void KeyEdit_MarkClean(void);static GUI_SECTION void KeyEdit_DeleteKey(Boolean saveBackup);static GUI_SECTION void KeyEdit_GetFields(void);#define k_KeyName 0#define k_Acct 1#define k_Passwd 2#define k_Notes 3#define k_NumFields 4// If the form is active, all these are valid.static ControlPtr f_DateTrg;static ScrollBarPtr f_NotesScrollBar;static FormPtr f_KeyEditForm;static Boolean f_dirty;static FieldPtr f_AllFields[k_NumFields];static CryptoKey *gRecordKey;static Boolean gKeyDirty;static DateType f_lastChanged;/* Index of the current record in the database as a whole. */static UInt16 gKeyRecordIndex = kNoRecord;/* True if we should sort the database on leaving this form. At the * moment we set this on any modification, although we could perhaps * be a bit more selective. */static Boolean f_needsSort;extern Boolean g_ReadOnly;// ======================================================================// Key edit form/* All of the text fields manage their own memory, including resizing * the allocations when required. We fill the struct containing * handles as records are read in, and use it to calculate lengths and * read values when writing out without having to repeatedly * interrogate the GUI fields. We have to cope specially with fields * which are null and created by the GUI, and also somehow check for * dirty fields or whether the whole record is empty and should be * deleted. *//* When we open a record for editing, we copy each field into a memory * chunk of its own so that the fields can resize them as necessary. * * If we're creating a new record, we don't need to do anything: the * fields can allocate their own working space and we will read out of * it if we save the record. * * When we save the record, we concatenate all the field chunks into a * record, and write that out. The field chunks are not free at that * point, because the gui field objects will free them when they are * freed themselves. * * If the user cancels, we don't need to do anything except go back to * the list view: the fields will deallocate their storage. * * When the user creates a new record, we allocate it immediately on * entering the form: this fits well with the user interface * abstraction, which is that a new blank record is allocated at that * point. It also means that all the rest of the code just has to * deal with a single case of editing an existing record. To get rid * of the record, the user must choose to delete it or (equivalently) * leave all the fields empty. *//* Update the form title to reflect the current record. If this is a * new record, it will be something like "New Record". If it's an * existing record, it will be something like "Record 4 of 42". * * TODO: Check if the following still applies: * - I haven't isolated the next as it is sometimes intermittent: a * bus error results after selecting a record from the list then * pressing Page Up key. I think it's somewhere in * KeyEdit_SetTitle(). -- dmgarner */static GUI_SECTION void KeyEdit_UpdateTitle(void){ MemHandle titleHandle; Char * titleTemplate; UInt16 pos, total; Char posStr[maxStrIToALen]; Char totalStr[maxStrIToALen]; Char * keyFormTitle; total = DmNumRecordsInCategory(gKeyDB, gPrefs.category); /* 1-based count */ if (gKeyRecordIndex == kNoRecord) pos = ++total; else pos = 1 + DmPositionInCategory(gKeyDB, gKeyRecordIndex, gPrefs.category); titleHandle = DmGetResource(strRsc, TitleTemplateStr); titleTemplate = MemHandleLock(titleHandle); ErrFatalDisplayIf(!titleTemplate, __FUNCTION__ ": no titleTemplate"); StrIToA(posStr, pos); StrIToA(totalStr, total); keyFormTitle = UI_TxtParamString(titleTemplate, posStr, totalStr, NULL, NULL); FrmCopyTitle(f_KeyEditForm, keyFormTitle); MemPtrFree(keyFormTitle); MemPtrUnlock(titleTemplate); DmReleaseResource(titleHandle); return;}/* Update the category popuptrigger to show the current record's * category name. */static GUI_SECTION void KeyEdit_UpdateCategory(void){ Category_UpdateName(f_KeyEditForm, gRecord->category);}/* Set the text in the "set date" popup trigger to match the date in * the record. */static GUI_SECTION void KeyEdit_SetDateTrigger(void){ static Char dateBuf[longDateStrLength]; DateFormatType fmt; Int16 year, month, day; fmt = (DateFormatType) PrefGetPreference(prefLongDateFormat); year = f_lastChanged.year + 1904; month = f_lastChanged.month; day = f_lastChanged.day; /* DateToAscii doesn't cope well if the date is unreasonable, so we * filter it a bit. */ if (month < 1 || month > 12 || day < 1 || day > 31) { month = day = 1; } DateToAscii(month, day, year, fmt, dateBuf); CtlSetLabel(f_DateTrg, dateBuf);}#define EVEN(x) (((x)+1)&~1)#define ENDMARKER 0xffff/* * Wipe out all the fields on this form. */static GUI_SECTION void KeyEdit_Clear(void){ gRecord = MemPtrNew(sizeof(UnpackedKeyType) + 4*sizeof(UInt16)); gRecord->numFields = 0; gRecord->plainText = MemPtrNew(2); *(UInt16 *)(gRecord->plainText) = ENDMARKER; gRecord->category = gPrefs.category; if (gRecord->category == dmAllCategories) gRecord->category = dmUnfiledCategory;}static GUI_SECTION void KeyEdit_ToUnpacked(void){ FieldHeaderType *field; int i, offset; int plainLen = EVEN(FldGetTextLength(f_AllFields[k_KeyName])) + EVEN(FldGetTextLength(f_AllFields[k_Acct])) + EVEN(FldGetTextLength(f_AllFields[k_Passwd])) + EVEN(FldGetTextLength(f_AllFields[k_Notes])) + 4 + 5 * sizeof(FieldHeaderType) + 2; UInt16 category = gRecord->category; MemWipe(gRecord->plainText, MemPtrSize(gRecord->plainText)); MemPtrFree(gRecord->plainText); MemWipe(gRecord, MemPtrSize(gRecord)); MemPtrFree(gRecord); gRecord = MemPtrNew(sizeof(UnpackedKeyType) + 4*sizeof(UInt16)); gRecord->numFields = 0; gRecord->plainText = MemPtrNew(plainLen); gRecord->category = category; offset = 0; for (i = 0; i < 5; i++) { gRecord->fieldOffset[gRecord->numFields++] = offset; field = gRecord->plainText + offset; field->fieldID = i; field->reserved = 0; offset += sizeof(FieldHeaderType); switch (i) { case 0: field->len = FldGetTextLength(f_AllFields[k_KeyName]); if (field->len) { MemMove((char *) (gRecord->plainText + offset), FldGetTextPtr(f_AllFields[k_KeyName]), field->len); offset += EVEN(field->len); } break; case 1: field->len = FldGetTextLength(f_AllFields[k_Acct]); if (field->len == 0) { offset -= sizeof(FieldHeaderType); gRecord->numFields--; } else { MemMove((char *) (gRecord->plainText + offset), FldGetTextPtr(f_AllFields[k_Acct]), field->len); offset += EVEN(field->len); } break; case 2: field->len = FldGetTextLength(f_AllFields[k_Passwd]); if (field->len == 0) { offset -= sizeof(FieldHeaderType); gRecord->numFields--; } else { MemMove((char *) (gRecord->plainText + offset), FldGetTextPtr(f_AllFields[k_Passwd]), field->len); offset += EVEN(field->len); } break; case 3: if (!f_lastChanged.year && !f_lastChanged.month && !f_lastChanged.day) { offset -= sizeof(FieldHeaderType); gRecord->numFields--; } else { field->len = sizeof(DateType); *(DateType*)(gRecord->plainText + offset) = f_lastChanged; offset += sizeof(DateType); } break; case 4: field->fieldID = NotesFieldID; field->len = FldGetTextLength(f_AllFields[k_Notes]); if (field->len == 0) { offset -= sizeof(FieldHeaderType); gRecord->numFields--; } else { MemMove((char *) (gRecord->plainText + offset), FldGetTextPtr(f_AllFields[k_Notes]), field->len); offset += EVEN(field->len); } break; } } *(UInt16 *)(gRecord->plainText + offset) = ENDMARKER;}#ifdef NOTIFY_SLEEP_HANDLERstatic GUI_SECTION Err KeyEdit_Sleep(SysNotifyParamType *np) { Err err; UInt16 cardNo; LocalID dbID; DmSearchStateType dss; /* Defer sleeping */ ((SleepEventParamType *) (np->notifyDetailsP))->deferSleep++; /* First enqueue events to restart application */ err = DmGetNextDatabaseByTypeCreator(true, &dss, sysFileTApplication, kKeyringCreatorID, true, &cardNo, &dbID); ErrNonFatalDisplayIf(err != errNone,"Failed to launch application!"); SysUIAppSwitch(cardNo, dbID, kKeyringResumeSleepLaunch, NULL); return 0;}#endif/* * Fill in the current KeyEdit form with data from the record * [gKeyRecordIndex]. We have to decrypt and unpack all the data. * gRecordKey already contains the decrypted record key. */static GUI_SECTION void KeyEdit_Load(void){ FormPtr busyForm; UInt16 attr; MemHandle record; busyForm = FrmInitForm(BusyDecryptForm); FrmSetActiveForm(busyForm); FrmDrawForm(busyForm); record = DmGetRecord(gKeyDB, gKeyRecordIndex); ErrFatalDisplayIf(!record, "couldn't query record"); if (Record_Unpack(record, &gRecord, gRecordKey)) KeyEdit_Clear(); DmRecordInfo(gKeyDB, gKeyRecordIndex, &attr, 0, 0); gRecord->category = (attr & dmRecAttrCategoryMask); FrmEraseForm(busyForm); FrmDeleteForm(busyForm); FrmSetActiveForm(f_KeyEditForm);}/* * Load the data for the current key into gRecord and update the form * elements. If this is a new key, go back to a blank form. */static GUI_SECTION void KeyEdit_FillField(FieldPtr field, char *data, unsigned int len) { MemHandle handle = MemHandleNew(len+1); char *ptr = MemHandleLock(handle); MemMove(ptr, data, len); ptr[len] = 0; MemHandleUnlock(handle); FldSetTextHandle(field, handle);}/* * Load the data for the current key into gRecord and update the form * elements. If this is a new key, go back to a blank form. */static GUI_SECTION void KeyEdit_FillData(void) { FieldHeaderType *fldHeader; unsigned int fldIndex, fldLen; gKeyDirty = false; if (gKeyRecordIndex == kNoRecord) KeyEdit_Clear(); else KeyEdit_Load(); if (gPrefs.category != dmAllCategories) gPrefs.category = gRecord->category; DateSecondsToDate(TimGetSeconds(), &f_lastChanged); for (fldIndex = 0; fldIndex < gRecord->numFields; fldIndex++) { fldHeader = (FieldHeaderType *) (gRecord->plainText + gRecord->fieldOffset[fldIndex]); fldLen = fldHeader->len; switch (fldHeader->fieldID) { case 0: /* key name */ KeyEdit_FillField(f_AllFields[k_KeyName], (char*) (fldHeader + 1), fldLen); break; case 1: /* account */ KeyEdit_FillField(f_AllFields[k_Acct], (char*) (fldHeader + 1), fldLen); break; case 2: /* password */ KeyEdit_FillField(f_AllFields[k_Passwd], (char*) (fldHeader + 1), fldLen); break; case 255: /* notes */ KeyEdit_FillField(f_AllFields[k_Notes], (char*) (fldHeader + 1), fldLen); break; case 3: /* lastChanged */ f_lastChanged = *(DateType*) (fldHeader + 1); break; } } KeyEdit_MarkClean(); KeyEdit_SetDateTrigger(); KeyEdit_UpdateCategory(); KeyEdit_UpdateTitle(); KeyEdit_UpdateScrollbar(); FrmDrawForm(f_KeyEditForm);}/* * Frees the GUI fields. Also overwrites everything with zeros. */static GUI_SECTION void KeyEdit_FreeFields(void) { int i; for (i = 0; i < k_NumFields; i++) { MemHandle textH = FldGetTextHandle(f_AllFields[i]); FldSetTextHandle(f_AllFields[i], NULL); if (textH) { MemWipe(MemHandleLock(textH), MemHandleSize(textH)); MemHandleUnlock(textH); MemHandleFree(textH); } }}/* * Save the record if any fields are dirty, and also update gRecord * from the field values. If the record has been left empty, then * delete it rather than saving an empty record. */static GUI_SECTION void KeyEdit_Commit(void){ if (KeyEdit_IsEmpty()) { KeyEdit_DeleteKey(false); /* no backup */ KeyEdit_MarkClean(); } else if (KeyEdit_IsDirty()) { if (gKeyRecordIndex == kNoRecord) { /* TODO: If this fails, do something. */ KeyDB_CreateNew(&gKeyRecordIndex); } f_needsSort = true; gKeyDirty = true; KeyEdit_ToUnpacked(); KeyEdit_Save(); KeyEdit_MarkClean(); }}/* Save values from the field into the database, after taking them * through an unpacked struct into encrypted form. The fields must * already be in gRecord-> * * Note that we're not *necessarily* leaving the form or even the * record at this point: pressing the Page buttons saves the record * too. */static GUI_SECTION void KeyEdit_Save(void) { FormPtr busyForm; busyForm = FrmInitForm(BusyEncryptForm); FrmSetActiveForm(busyForm); FrmDrawForm(busyForm); Record_SaveRecord(gRecord, gKeyRecordIndex, gRecordKey); FrmEraseForm(busyForm); FrmDeleteForm(busyForm); FrmSetActiveForm(f_KeyEditForm);}/* * Mark all fields as clean; called just after we save */static GUI_SECTION void KeyEdit_MarkClean(void){ Int16 i; for (i = 0; i < k_NumFields; i++) { FldSetDirty(f_AllFields[i], false); } f_dirty = false;}/* Check if any fields are dirty, i.e. have been modified by the * user. */static GUI_SECTION Boolean KeyEdit_IsDirty(void){ Int16 i; if (f_dirty) return true; for (i = 0; i < k_NumFields; i++) { if (FldDirty(f_AllFields[i])) return true; } return false;}/* Check if all fields are empty. If so, when leaving we will discard * the record rather than saving it. */static GUI_SECTION Boolean KeyEdit_IsEmpty(void){ Int16 i; for (i = 0; i < k_NumFields; i++) { if (FldGetTextLength(f_AllFields[i])) return false; } return true;}/* * Go to the record with the specified index. It has to work * regardless of the currently active form. If key edit form is * already active it commits the last record before attempting to * switch the record. * * If the timeout has passed then we do not allow a new record to be * opened. The user must either re-enter their password, or he gets * transferred to the key list. */GUI_SECTION void KeyEdit_GotoRecord(UInt16 recordIdx){ /* If we are active, commit the current record. */ if (gEditFormActive) { KeyEdit_Commit(); /* Check that database is still unlocked */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -