/* * A tool to compute and manage PBKDF2 values as used in WPA-PSK and WPA2-PSK * * Copyright (C) 2007; 2008, 2009 ebfe * * 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 * * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. * If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. * If you * do not wish to do so, delete this exception statement from your * version. * If you delete this exception statement from all source * files in the program, then also delete it here. */ #include #include #include #include #include #include #include #include #include #include #include #include "aircrack-ng.h" #include "crypto.h" #ifdef HAVE_REGEXP #include #endif #include "version.h" #define IMPORT_ESSID "essid" #define IMPORT_PASSWD "passwd" #define IMPORT_COWPATTY "cowpatty" extern char * getVersion(char * progname, int maj, int min, int submin, int svnrev, int beta, int rc); void print_help(const char * msg) { printf("\n" " %s - (C) 2007, 2008, 2009 ebfe\n" " http://www.aircrack-ng.org\n" "\n" " Usage: airolib-ng [options]\n" "\n" " Operations:\n" "\n" " --stats : Output information about the database.\n" " --sql : Execute specified SQL statement.\n" " --clean [all] : Clean the database from old junk. 'all' will also \n" " reduce filesize if possible and run an integrity check.\n" " --batch : Start batch-processing all combinations of ESSIDs\n" " and passwords.\n" " --verify [all] : Verify a set of randomly chosen PMKs.\n" " If 'all' is given, all invalid PMK will be deleted.\n" "\n" " --import [essid|passwd] :\n" " Import a text file as a list of ESSIDs or passwords.\n" " --import cowpatty :\n" " Import a cowpatty file.\n" "\n" " --export cowpatty :\n" " Export to a cowpatty file.\n" "\n", getVersion("Airolib-ng", _MAJ, _MIN, _SUB_MIN, _REVISION, _BETA, _RC)); if (msg && strlen(msg) > 0) { printf("%s", msg); puts(""); } } void sql_error(sqlite3* db) { fprintf(stderr, "Database error: %s\n", sqlite3_errmsg(db)); } int sql_exec_cb(sqlite3* db, const char *sql, void* callback, void* cb_arg) { #ifdef SQL_DEBUG printf(sql); printf("\n"); fflush(stdout); #endif int rc; char *zErrMsg = 0; char looper[4] = {'|','/','-','\\'}; int looperc = 0; int waited = 0; while (1) { rc = sqlite3_exec(db,sql,callback,cb_arg,&zErrMsg); if (rc == SQLITE_LOCKED || rc == SQLITE_BUSY) { fprintf(stdout,"Database is locked or busy. Waiting %is ... %1c \r",++waited, looper[looperc++ % sizeof(looper)]); fflush(stdout); sleep(1); } else { if (rc != SQLITE_OK) { fprintf(stderr, "SQL error. %s\n", zErrMsg); sqlite3_free(zErrMsg); } if (waited != 0) printf("\n\n"); return rc; } } } // execute sql fast and hard. int sql_exec(sqlite3* db, const char *sql) { return sql_exec_cb(db,sql,0,0); } // wrapper for sqlite3_step which retries executing statements if the db returns SQLITE_BUSY or SQLITE_LOCKED int sql_step(sqlite3_stmt* stmt, int wait) { int rc; char looper[4] = {'|','/','-','\\'}; int looperc = 0; int waited = 0; while (1) { rc = sqlite3_step(stmt); if (rc == SQLITE_LOCKED || rc == SQLITE_BUSY) { if (wait != 0) { fprintf(stdout,"Database is locked or busy. Waiting %is ... %1c \r",++waited, looper[looperc]); fflush(stdout); wait--; looperc = looperc+1 % sizeof(looper); sleep(1); } else { fprintf(stderr,"Database was locked or busy while getting results. I've given up.\n"); return rc; } } else { if (waited != 0) printf("\n\n"); return rc; } } } // wrapper for sqlite3_prepare_v2 which retries creating statements if the db returns SQLITE_BUSY or SQLITE_LOCKED int sql_prepare(sqlite3 *db, const char *sql, sqlite3_stmt **ppStmt, int wait) { #ifdef SQL_DEBUG printf(sql); printf("\n"); fflush(stdout); #endif int rc; char looper[4] = {'|','/','-','\\'}; int looperc = 0; int waited = 0; while (1) { rc = sqlite3_prepare_v2(db,sql,-1,ppStmt,NULL); if (rc == SQLITE_LOCKED || rc == SQLITE_BUSY) { if (wait != 0) { fprintf(stdout,"Database is locked or busy. Waiting %is ... %1c \r", ++waited, looper[looperc]); fflush(stdout); wait--; looperc = looperc+1 % sizeof(looper); sleep(1); } else { fprintf(stderr,"Database was locked or busy while creating statement. I've given up.\n"); return rc; } } else { if (waited != 0) printf("\n\n"); return rc; } } } // generic function to dump a resultset including column names to stdout int stmt_stdout(sqlite3_stmt* stmt, int* rowcount) { int ccount; int rcount = 0; int rc; if (stmt == 0 || (ccount = sqlite3_column_count(stmt)) == 0) { return sql_step(stmt,0); } int i = 0; do { printf("%s", sqlite3_column_name(stmt,i++)); if (i < ccount) printf("\t"); } while (i < ccount); printf("\n"); while ((rc = sql_step(stmt,0)) == SQLITE_ROW) { i = 0; rcount++; do { printf("%s", (char *)sqlite3_column_text(stmt,i++)); if (i < ccount) printf("\t"); } while (i < ccount); printf("\n"); } if (rowcount != NULL) *rowcount=rcount; return rc; } // generic function to dump the output of a sql statement to stdout. // will return sqlite error codes but also handle (read: ignore) them itself int sql_stdout(sqlite3* db, const char* sql, int* rowcount) { int rc; sqlite3_stmt *stmt; rc = sql_prepare(db,sql,&stmt,-1); if (rc != SQLITE_OK) { sql_error(db); return rc; } rc = stmt_stdout(stmt,rowcount); sqlite3_finalize(stmt); if (rc == SQLITE_DONE) { if (sqlite3_changes(db) > 0) fprintf(stdout,"Query done. %i rows affected.",sqlite3_changes(db)); } else { sql_error(db); } printf("\n"); return rc; } // retrieve a single int value using a sql query. // returns 0 if something goes wrong. beware! create your own statement if you need error handling. int query_int(sqlite3* db, const char* sql) { sqlite3_stmt *stmt; int rc; int ret; rc = sql_prepare(db,sql,&stmt,-1); if (rc != SQLITE_OK || stmt == 0 || sqlite3_column_count(stmt) == 0) { sql_error(db); ret = 0; } else { rc = sql_step(stmt,-1); if (rc == SQLITE_ROW) { ret = sqlite3_column_int(stmt,0); } else { #ifdef SQL_DEBUG printf("DEBUG: query_int() returns with sql_step() != SQLITE_ROW\n"); #endif ret = 0; } } sqlite3_finalize(stmt); return ret; } // throw some statistics about the db to stdout. // if precise!=0 the stats will be queried nail by nail which can be slow void show_stats(sqlite3* db, int precise) { sql_exec(db,"BEGIN;"); int essids = query_int(db, "SELECT COUNT(*) FROM essid;"); int passwds = query_int(db,"SELECT COUNT(*) FROM passwd;"); int done; if (precise != 0) { printf("Determining precise statistics may be slow...\n"); done = query_int(db, "SELECT COUNT(*) FROM essid,passwd INNER JOIN pmk ON pmk.essid_id = essid.essid_id AND pmk.passwd_id = passwd.passwd_id"); } else { done = query_int(db, "SELECT COUNT(*) FROM pmk;"); } fprintf(stdout,"There are %i ESSIDs and %i passwords in the database. %i out of %i possible combinations have been computed (%g%%).\n\n", essids, passwds, done, essids*passwds, essids*passwds > 0 ? ((double)done*100)/(essids*passwds) : 0); if (precise != 0) { sql_stdout(db, "select essid.essid AS ESSID, essid.prio AS Priority, round(count(pmk.essid_id) * 100.0 / count(*),2) AS Done from essid,passwd left join pmk on pmk.essid_id = essid.essid_id and pmk.passwd_id = passwd.passwd_id group by essid.essid_id;",0); } else { sql_stdout(db, "SELECT essid.essid AS ESSID, essid.prio AS Priority, ROUND(COUNT(pmk.essid_id) * 100.0 / (SELECT COUNT(*) FROM passwd),2) AS Done FROM essid LEFT JOIN pmk ON pmk.essid_id = essid.essid_id GROUP BY essid.essid_id;",0); } sql_exec(db,"COMMIT;"); } /* batch-process all combinations of ESSIDs and PASSWDs. this function may be called only once per db at the same time, yet multiple processes can batch-process a single db. don't modify this function's layout or it's queries without carefully considering speed, efficiency and concurrency. */ void batch_process(sqlite3* db) { int rc; int cur_essid = 0; struct timeval starttime; struct timeval curtime; gettimeofday(&starttime,NULL); int rowcount = 0; char *sql; if (sql_exec(db, "CREATE TEMPORARY TABLE temp.buffer (wb_id integer, essid_id integer, passwd_id integer, essid text, passwd text, pmk blob);") != SQLITE_OK) { fprintf(stderr,"Failed to create buffer for batch processing.\n"); return; } // may fail - thats ok cur_essid = query_int(db,"SELECT essid_id FROM workbench LIMIT 1;"); while(1) { //loop over everything do { //loop over ESSID do { //loop over workbench sql_exec(db,"DELETE FROM temp.buffer;"); // select some work from the workbench into our own buffer // move lockid ahead so other clients won't get those rows any time soon sql_exec(db,"BEGIN EXCLUSIVE;"); sql_exec(db,"INSERT INTO temp.buffer (wb_id,essid_id,passwd_id,essid,passwd) SELECT wb_id, essid.essid_id,passwd.passwd_id,essid,passwd FROM workbench CROSS JOIN essid ON essid.essid_id = workbench.essid_id CROSS JOIN passwd ON passwd.passwd_id = workbench.passwd_id ORDER BY lockid LIMIT 25000;"); sql_exec(db,"UPDATE workbench SET lockid=lockid+1 WHERE wb_id IN (SELECT wb_id FROM buffer);"); sql_exec(db,"COMMIT;"); rc = query_int(db,"SELECT COUNT(*) FROM buffer;"); if (rc > 0) { // now calculate all the PMKs with a single statement. // remember the update won't lock the db sql_exec(db,"UPDATE temp.buffer SET pmk = PMK(essid,passwd);"); // commit work and delete package from workbench sql_exec(db,"BEGIN EXCLUSIVE;"); sql_exec(db,"INSERT OR IGNORE INTO pmk (essid_id,passwd_id,pmk) SELECT essid_id,passwd_id,pmk FROM temp.buffer"); sql_exec(db,"DELETE FROM workbench WHERE wb_id IN (SELECT wb_id FROM buffer);"); sql_exec(db,"COMMIT;"); rowcount += rc; gettimeofday(&curtime,NULL); int timediff = curtime.tv_sec - starttime.tv_sec; fprintf(stdout,"\rComputed %i PMK in %i seconds (%i PMK/s, %i in buffer). ",rowcount,timediff, timediff > 0 ? rowcount / timediff : rowcount, query_int(db,"SELECT COUNT(*) FROM workbench;")); fflush(stdout); } } while (rc > 0); sql = sqlite3_mprintf("INSERT OR IGNORE INTO workbench (essid_id,passwd_id) SELECT essid.essid_id,passwd.passwd_id FROM passwd CROSS JOIN essid LEFT JOIN pmk ON pmk.essid_id = essid.essid_id AND pmk.passwd_id = passwd.passwd_id WHERE essid.essid_id = %i AND pmk.essid_id IS NULL LIMIT 250000;",cur_essid); sql_exec(db,sql); sqlite3_free(sql); } while (query_int(db,"SELECT COUNT(*) FROM workbench INNER JOIN essid ON essid.essid_id = workbench.essid_id INNER JOIN passwd ON passwd.passwd_id = workbench.passwd_id;") > 0); cur_essid = query_int(db,"SELECT essid.essid_id FROM essid LEFT JOIN pmk USING (essid_id) WHERE VERIFY_ESSID(essid.essid) == 0 GROUP BY essid.essid_id HAVING COUNT(pmk.essid_id) < (SELECT COUNT(*) FROM passwd) ORDER BY essid.prio,COUNT(pmk.essid_id),RANDOM() LIMIT 1;"); if (cur_essid == 0) { printf("All ESSID processed.\n\n"); sqlite3_close(db); exit(0); /* printf("No free ESSID found. Will try determining new ESSID in 5 minutes...\n"); sleep(60*5); // slower, yet certain. should never be any better than the above, unless users fumble with the db. cur_essid = query_int(db,"SELECT essid.essid_id FROM essid,passwd LEFT JOIN pmk ON pmk.essid_id = essid.essid_id AND pmk.passwd_id = passwd.passwd_id WHERE pmk.essid_id IS NULL LIMIT 1;"); if (cur_essid == 0) { printf("No free ESSID found. Sleeping 25 additional minutes...\n"); sleep(60*25); } */ } } //never reached sql_exec(db,"DROP TABLE temp.buffer;"); } // Verify an ESSID. Returns 1 if ESSID is invalid. //TODO More things to verify? Invalid chars? int verify_essid(char* essid) { return essid == NULL || strlen(essid) < 1 || strlen(essid) > 32; } // sql function which checks a given ESSID void sql_verify_essid(sqlite3_context* context, int argc, sqlite3_value** values) { char* essid = (char*)sqlite3_value_text(values[0]); if (argc != 1 || essid == 0) { fprintf(stderr,"SQL function VERIFY_ESSID called with invalid arguments"); return; } sqlite3_result_int(context,verify_essid(essid)); } int verify_passwd(char* passwd) { return passwd == NULL || strlen(passwd) < 8 || strlen(passwd) > 63; } void sql_verify_passwd(sqlite3_context* context, int argc, sqlite3_value** values) { char* passwd = (char*)sqlite3_value_text(values[0]); if (argc != 1 || passwd == 0) { fprintf(stderr,"SQL function VERIFY_PASSWD called with invalid arguments"); return; } sqlite3_result_int(context,verify_passwd(passwd)); } // clean the db, analyze, maybe vacuum and check void vacuum(sqlite3* db, int deep) { printf("Deleting invalid ESSIDs and passwords...\n"); sql_exec(db, "DELETE FROM essid WHERE VERIFY_ESSID(essid) != 0;"); sql_exec(db, "DELETE FROM passwd WHERE VERIFY_PASSWD(passwd) != 0"); printf("Deleting unreferenced PMKs...\n"); sql_exec(db, "DELETE FROM pmk WHERE essid_id NOT IN (SELECT essid_id FROM essid)"); sql_exec(db, "DELETE FROM pmk WHERE passwd_id NOT IN (SELECT passwd_id FROM passwd)"); printf("Analysing index structure...\n"); sql_exec(db, "ANALYZE;"); if (deep != 0) { printf("Vacuum-cleaning the database. This could take a while...\n"); sql_exec(db, "VACUUM;"); printf("Checking database integrity...\n"); sql_stdout(db, "PRAGMA integrity_check;",0); } printf("Done.\n"); } // verify PMKs. If complete==1 we check all PMKs // returns 0 if ok, !=0 otherwise void verify(sqlite3* db, int complete) { if (complete != 1) { printf("Checking ~10.000 randomly chosen PMKs...\n"); // this is faster than 'order by random()'. we need the subquery to trick the optimizer... sql_stdout(db,"select s.essid AS ESSID, COUNT(*) AS CHECKED, CASE WHEN MIN(s.pmk == PMK(essid,passwd)) == 0 THEN 'FAILED' ELSE 'OK' END AS STATUS FROM (select distinct essid,passwd,pmk FROM pmk INNER JOIN passwd ON passwd.passwd_id = pmk.passwd_id INNER JOIN essid ON essid.essid_id = pmk.essid_id WHERE abs(random() % (select count(*) from pmk)) < 10000) AS s GROUP BY s.essid;",0); } else { printf("Checking all PMKs. This could take a while...\n"); sql_stdout(db,"select essid AS ESSID,passwd AS PASSWORD,HEX(pmk) AS PMK_DB, HEX(PMK(essid,passwd)) AS CORRECT FROM pmk INNER JOIN passwd ON passwd.passwd_id = pmk.passwd_id INNER JOIN essid ON essid.essid_id = pmk.essid_id WHERE pmk.pmk != PMK(essid,passwd);",0); } } // callback for export_cowpatty. takes the passwd and pmk from the query and writes another fileentry. int sql_exportcow(void* arg, int ccount, char** values, char** columnnames) { FILE *f = (FILE*)arg; struct hashdb_rec rec; if (ccount != 2 || values[0] == NULL || values[1] == NULL || fileno(f) == -1) { printf("Illegal call to sql_exportcow.\n"); return -1; } if (columnnames) {} //XXX char* passwd = (char*)values[0]; memcpy(rec.pmk,values[1],sizeof(rec.pmk)); rec.rec_size = strlen(passwd) + sizeof(rec.pmk)+ sizeof(rec.rec_size); int rc = fwrite(&rec.rec_size,sizeof(rec.rec_size),1,f); rc += fwrite(passwd, strlen(passwd),1,f); rc += fwrite(rec.pmk, sizeof(rec.pmk), 1, f); if (rc != 3) { printf("Error while writing to export file. Query aborted...\n"); return 1; } fflush(f); return 0; } // export to a cowpatty file void export_cowpatty(sqlite3* db, char* essid, char* filename) { struct hashdb_head filehead; memset(&filehead, 0, sizeof(filehead)); FILE *f = NULL; if (access(filename, F_OK)==0) { printf("The file already exists and I won't overwrite it.\n"); return; } // ensure that the essid is found in the db and has at least one entry in the pmk table. char *sql = sqlite3_mprintf("SELECT COUNT(*) FROM (SELECT passwd, pmk FROM essid,passwd INNER JOIN pmk ON pmk.passwd_id = passwd.passwd_id AND pmk.essid_id = essid.essid_id WHERE essid.essid = '%q' LIMIT 1);",essid); int rc = query_int(db,sql); sqlite3_free(sql); if (rc == 0) { printf("There is no such ESSID in the database or there are no PMKs for it.\n"); return; } memcpy(filehead.ssid, essid,strlen(essid)); filehead.ssidlen = strlen(essid); filehead.magic = GENPMKMAGIC; f = fopen(filename, "w"); if (f == NULL || fwrite(&filehead, sizeof(filehead), 1, f) != 1) { printf("Couldn't open the export file for writing.\n"); return; } // as we have an open filehandle, we now query the db to return passwds and associated PMKs for that essid. we pass the filehandle to a callback function which will write the rows to the file. sql = sqlite3_mprintf("SELECT passwd, pmk FROM essid,passwd INNER JOIN pmk ON pmk.passwd_id = passwd.passwd_id AND pmk.essid_id = essid.essid_id WHERE essid.essid = '%q'",essid); printf("Exporting...\n"); rc = sql_exec_cb(db,sql,&sql_exportcow,f); sqlite3_free(sql); if (rc != SQLITE_OK) { printf("There was an error while exporting.\n"); } fclose(f); printf("Done.\n"); } // import a cowpatty file int import_cowpatty(sqlite3* db, char* filename) { struct hashdb_head filehead; struct hashdb_rec rec; FILE *f = NULL; int rc; sqlite3_stmt *stmt; char* sql; int essid_id; int wordlength; char passwd[63+1]; if (strcmp(filename,"-") == 0) { f = stdin; } else { f = fopen(filename, "r"); } if (f == NULL || fread(&filehead, sizeof(filehead),1,f) != 1) { printf("Couldn't open the import file for reading.\n"); return 0; } else if (filehead.magic != GENPMKMAGIC) { printf("File doesn't seem to be a cowpatty file.\n"); fclose(f); return 0; } else if (verify_essid((char *)filehead.ssid) != 0) { printf("The file's ESSID is invalid.\n"); fclose(f); return 0; } printf("Reading header...\n"); //We need protection so concurrent transactions can't smash the ID-references sql_exec(db,"BEGIN;"); sql = sqlite3_mprintf("INSERT OR IGNORE INTO essid (essid) VALUES ('%q');",filehead.ssid); sql_exec(db,sql); sqlite3_free(sql); //since there is only one essid per file, we can determine it's ID now sql = sqlite3_mprintf("SELECT essid_id FROM essid WHERE essid = '%q'", filehead.ssid); essid_id = query_int(db,sql); sqlite3_free(sql); if (essid_id == 0) { fclose(f); sql_exec(db,"ROLLBACK;"); printf("ESSID couldn't be inserted. I've given up.\n"); return 0; } sql = sqlite3_mprintf("CREATE TEMPORARY TABLE import (passwd text, pmk blob);", essid_id); sql_exec(db,sql); sqlite3_free(sql); sql_prepare(db,"INSERT INTO import (passwd,pmk) VALUES (@pw,@pmk)",&stmt,-1); printf("Reading...\n"); while ((rc = fread(&rec.rec_size, sizeof(rec.rec_size), 1, f)) == 1) { wordlength = abs(rec.rec_size) - (sizeof(rec.pmk) + sizeof(rec.rec_size)); //prevent out of bounds writing (sigsegv guaranteed) but don't skip the whole file if wordlength < 8 if (wordlength > 0 && wordlength < (int) sizeof(passwd)) { passwd[wordlength] = 0; rc += fread(passwd, wordlength, 1, f); if (rc == 2) rc += fread(&rec.pmk, sizeof(rec.pmk), 1, f); } if (rc != 3) { fprintf(stdout,"Error while reading record (%i).\n",rc); sqlite3_finalize(stmt); if (db == NULL) { printf("omg"); fflush(stdout); } sql_exec(db, "ROLLBACK;"); fclose(f); return 1; } if (verify_passwd(passwd) == 0) { sqlite3_bind_text(stmt,1,passwd, strlen(passwd),SQLITE_TRANSIENT); sqlite3_bind_blob(stmt,2,&rec.pmk, sizeof(rec.pmk),SQLITE_TRANSIENT); if (sql_step(stmt,-1) == SQLITE_DONE) { sqlite3_reset(stmt); } else { printf("Error while inserting record into database.\n"); sqlite3_finalize(stmt); sql_exec(db, "ROLLBACK;"); fclose(f); return 1; } } else { fprintf(stdout,"Invalid password %s will not be imported.\n",passwd); } } sqlite3_finalize(stmt); if (!feof(f)) { printf("Error while reading file.\n"); sql_exec(db,"ROLLBACK;"); fclose(f); return 1; } printf("Updating references...\n"); sql_exec(db, "INSERT OR IGNORE INTO passwd (passwd) SELECT passwd FROM import;"); //TODO Give the user a choice to either INSERT OR UPDATE or INSERT OR IGNORE printf("Writing...\n"); sql = sqlite3_mprintf("INSERT OR IGNORE INTO pmk (essid_id,passwd_id,pmk) SELECT %i,passwd.passwd_id,import.pmk FROM import INNER JOIN passwd ON passwd.passwd = import.passwd;",essid_id); sql_exec(db,sql); sqlite3_free(sql); sql_exec(db,"COMMIT;"); fclose(f); return 1; } int import_ascii(sqlite3* db, const char* mode, const char* filename) { FILE *f = NULL; sqlite3_stmt *stmt; char buffer[63+1]; int imported=0; int ignored=0; int imode=0; if (strcasecmp(mode,IMPORT_ESSID) == 0) { imode = 0; } else if (strcasecmp(mode,IMPORT_PASSWD) == 0) { imode = 1; } else { printf("Specify either 'essid' or 'passwd' as import mode.\n"); return 0; } if (strcmp(filename,"-") == 0) { f = stdin; } else { f = fopen(filename, "r"); } if (f == NULL) { printf("Could not open file/stream for reading.\n"); return 0; } char* sql = sqlite3_mprintf("INSERT OR IGNORE INTO %q (%q) VALUES (@v);",mode,mode); sql_prepare(db,sql,&stmt,-1); sqlite3_free(sql); sql_exec(db, "BEGIN;"); printf("Reading file...\n"); while (fgets(buffer, sizeof(buffer), f) != 0) { int i = strlen(buffer); if (buffer[i-1] == '\n') buffer[--i] = '\0'; if (buffer[i-1] == '\r') buffer[--i] = '\0'; imported++; if ((imode == 0 && verify_essid(buffer)==0) || (imode == 1 && verify_passwd(buffer)==0)) { sqlite3_bind_text(stmt,1,buffer, strlen(buffer),SQLITE_TRANSIENT); if (sql_step(stmt,-1) == SQLITE_DONE) { sqlite3_reset(stmt); } else { printf("Error while inserting record into database.\n"); sql_exec(db, "ROLLBACK;"); sqlite3_finalize(stmt); fclose(f); return 1; } } else { ignored++; } if (imported % 1000 == 0) { fprintf(stdout,"%i lines read, %i invalid lines ignored.\r",imported,ignored); fflush(stdout); } } sqlite3_finalize(stmt); if (!feof(f)) { printf("Error while reading file.\n"); sql_exec(db,"ROLLBACK;"); fclose(f); return 1; } fclose(f); printf("Writing...\n"); sql_exec(db,"COMMIT;"); printf("Done.\n"); return 1; } // sql function. takes ESSID and PASSWD, gives PMK void sql_calcpmk(sqlite3_context* context, int argc, sqlite3_value** values) { unsigned char pmk[40]; char* passwd = (char*)sqlite3_value_blob(values[1]); char* essid = (char*)sqlite3_value_blob(values[0]); if (argc < 2 || passwd == 0 || essid == 0) { sqlite3_result_error(context, "SQL function PMK() called with invalid arguments.\n", -1); return; } calc_pmk(passwd,essid,pmk); sqlite3_result_blob(context,pmk,32,SQLITE_TRANSIENT); } #ifdef HAVE_REGEXP void sqlite_regexp(sqlite3_context* context, int argc, sqlite3_value** values) { int ret; regex_t regex; char* reg = (char*)sqlite3_value_text(values[0]); char* text = (char*)sqlite3_value_text(values[1]); if ( argc != 2 || reg == 0 || text == 0) { sqlite3_result_error(context, "SQL function regexp() called with invalid arguments.\n", -1); return; } ret = regcomp(®ex, reg, REG_EXTENDED | REG_NOSUB); if ( ret != 0 ) { sqlite3_result_error(context, "error compiling regular expression", -1); return; } ret = regexec(®ex, text , 0, NULL, 0); regfree(®ex); sqlite3_result_int(context, (ret != REG_NOMATCH)); } #endif int initDataBase(const char * filename, sqlite3 ** db) { //int rc = sqlite3_open_v2(filename, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); int rc = sqlite3_open(filename, &(*db)); if (rc != SQLITE_OK) { sql_error(*db); sqlite3_close(*db); // May be usefull later return rc; } sql_exec(*db, "create table essid (essid_id integer primary key autoincrement, essid text, prio integer default 64);"); sql_exec(*db, "create table passwd (passwd_id integer primary key autoincrement, passwd text);"); sql_exec(*db, "create table pmk (pmk_id integer primary key autoincrement, passwd_id int, essid_id int, pmk blob);"); sql_exec(*db, "create table workbench (wb_id integer primary key autoincrement, essid_id integer, passwd_id integer, lockid integer default 0);"); sql_exec(*db, "create index lock_lockid on workbench (lockid);"); sql_exec(*db, "create index pmk_pw on pmk (passwd_id);"); sql_exec(*db, "create unique index essid_u on essid (essid);"); sql_exec(*db, "create unique index passwd_u on passwd (passwd);"); sql_exec(*db, "create unique index ep_u on pmk (essid_id,passwd_id);"); sql_exec(*db, "create unique index wb_u on workbench (essid_id,passwd_id);"); sql_exec(*db, "CREATE TRIGGER delete_essid DELETE ON essid BEGIN DELETE FROM pmk WHERE pmk.essid_id = OLD.essid_id; DELETE FROM workbench WHERE workbench.essid_id = OLD.essid_id; END;"); sql_exec(*db, "CREATE TRIGGER delete_passwd DELETE ON passwd BEGIN DELETE FROM pmk WHERE pmk.passwd_id = OLD.passwd_id; DELETE FROM workbench WHERE workbench.passwd_id = OLD.passwd_id; END;"); #ifdef SQL_DEBUG sql_exec(*db, "begin;"); sql_exec(*db, "insert into essid (essid,prio) values ('e',random())"); sql_exec(*db, "insert into passwd (passwd) values ('p')"); sql_exec(*db, "insert into essid (essid,prio) select essid||'a',random() from essid;"); sql_exec(*db, "insert into essid (essid,prio) select essid||'b',random() from essid;"); sql_exec(*db, "insert into essid (essid,prio) select essid||'c',random() from essid;"); sql_exec(*db, "insert into essid (essid,prio) select essid||'d',random() from essid;"); sql_exec(*db, "insert into passwd (passwd) select passwd||'a' from passwd;"); sql_exec(*db, "insert into passwd (passwd) select passwd||'b' from passwd;"); sql_exec(*db, "insert into passwd (passwd) select passwd||'c' from passwd;"); sql_exec(*db, "insert into passwd (passwd) select passwd||'d' from passwd;"); sql_exec(*db, "insert into passwd (passwd) select passwd||'e' from passwd;"); sql_exec(*db, "insert into pmk (essid_id,passwd_id) select essid_id,passwd_id from essid,passwd limit 1000000;"); sql_exec(*db,"commit;"); #endif sqlite3_close(*db); printf("Database <%s> sucessfully created\n", filename); return 0; } int check_for_db(sqlite3 ** db, const char * filename, int can_create, int readonly) { struct stat dbfile; int rc; int accessflags = R_OK | W_OK; if (readonly) accessflags = R_OK; // Check if DB exist. If it does not, initialize it if (access(filename, accessflags)) { printf("Database <%s> does not already exist, ", filename); if (can_create) { printf("creating it...\n"); rc = initDataBase(filename, db); if (rc) { printf("Error initializing database (return code: %d), exiting...\n", rc); return 1; } } else { printf("exiting ...\n"); return 1; } } else { if (stat(filename, &dbfile)) { perror("stat()"); return 1; } if ((S_ISREG(dbfile.st_mode) && !S_ISDIR(dbfile.st_mode)) == 0) { printf("\"%s\" does not appear to be a file.\n", filename); return 1; } } rc = sqlite3_open(filename, &(*db)); if(rc) { sql_error(*db); sqlite3_close(*db); return 1; } // TODO: Sanity check: Table definitions, index // register new functions to be used in SQL statements if (sqlite3_create_function(*db, "PMK", 2, SQLITE_ANY, 0, &sql_calcpmk,0,0) != SQLITE_OK) { printf("Failed creating PMK function.\n"); sql_error(*db); sqlite3_close(*db); return 1; } if (sqlite3_create_function(*db, "VERIFY_ESSID", 1, SQLITE_ANY, 0, &sql_verify_essid,0,0) != SQLITE_OK) { printf("Failed creating VERIFY_ESSID function.\n"); sql_error(*db); sqlite3_close(*db); return 1; } if (sqlite3_create_function(*db, "VERIFY_PASSWD", 1, SQLITE_ANY, 0, &sql_verify_passwd,0,0) != SQLITE_OK) { printf("Failed creating VERIFY_PASSWD function.\n"); sql_error(*db); sqlite3_close(*db); return 1; } #ifdef HAVE_REGEXP if (sqlite3_create_function(*db, "regexp", 2, SQLITE_ANY,0, &sqlite_regexp,0,0) != SQLITE_OK) { printf("Failed creating regexp() handler.\n"); sql_error(*db); sqlite3_close(*db); return 1; } #endif return 0; } int main(int argc, char **argv) { sqlite3 *db; int option_index, option; if( argc < 3 ){ print_help(NULL); return 1; } db = NULL; option_index = 0; static struct option long_options[] = { {"batch", 0, 0, 'b'}, {"clean", 2, 0, 'c'}, {"export", 2, 0, 'e'}, {"h", 0, 0, 'h'}, {"help", 0, 0, 'h'}, {"import", 2, 0, 'i'}, {"sql", 1, 0, 's'}, {"stats", 2, 0, 't'}, {"statistics", 2, 0, 't'}, {"verify", 2, 0, 'v'}, {"vacuum", 2, 0, 'c'}, // TODO: implement options like '-e essid' to limit // operations to a certain essid where possible {"essid", 1, 0, 'd'}, {0, 0, 0, 0 } }; option = getopt_long( argc, argv, "bc:d:e:hi:s:t:v:", long_options, &option_index ); if( option > 0 ) { switch (option) { case 'b': // Batch if ( check_for_db(&db, argv[1], 0, 1) ) { return 1; } batch_process(db); break; case 'c': // Clean if ( check_for_db(&db, argv[1], 0, 0) ) { return 1; } vacuum(db, (argc > 3 && strcasecmp(argv[3],"all") == 0) ? 1 : 0); break; case 'e': if (argc < 4) { print_help("You must specify an export format."); } else if (strcmp(argv[3],"cowpatty")==0) { if (argc < 6) { print_help("You must specify essid and output file."); } else { // Export if ( check_for_db(&db, argv[1], 0, 0) ) { return 1; } export_cowpatty(db,argv[4],argv[5]); } } else { print_help("Invalid export format specified."); } break; case ':' : case '?' : case 'h': // Show help print_help(NULL); break; case 'i': // Import if (argc < 5) { print_help("You must specifiy an import format and a file."); } else if (strcasecmp(argv[3], IMPORT_COWPATTY) == 0) { if ( check_for_db(&db, argv[1], 1, 0) ) { return 1; } import_cowpatty(db,argv[4]); } else if (strcasecmp(argv[3], IMPORT_ESSID) == 0) { if ( check_for_db(&db, argv[1], 1, 0) ) { return 1; } import_ascii(db, IMPORT_ESSID,argv[4]); } else if (strcasecmp(argv[3], IMPORT_PASSWD) == 0 || strcasecmp(argv[3],"password") == 0) { if ( check_for_db(&db, argv[1], 1, 0) ) { return 1; } import_ascii(db,IMPORT_PASSWD, argv[4]); } else { print_help("Invalid import format specified."); return 1; } break; case 's': // SQL // We don't know if the SQL order is changing the file or not if ( check_for_db(&db, argv[1], 0, 0) ) { return 1; } sql_stdout(db, argv[3], 0); break; case 't': // Stats if ( check_for_db(&db, argv[1], 0, 1) ) { return 1; } show_stats(db, (argv[3] == NULL) ? 0 : 1); break; case 'v': // Verify if ( check_for_db(&db, argv[1], 0, (argc > 3 && strcasecmp(argv[3],"all")==0) ? 0 : 1) ) { return 1; } verify(db, (argc > 3 && strcasecmp(argv[3],"all")==0) ? 1 : 0); break; default: print_help("Invalid option"); break; } } else { print_help(NULL); } if (db) sqlite3_close(db); return 0; }