/* wifiscan: Put the data from wifi_autoconnect.sh and display them nice and sorted by row n=0, 1, ... User Input: Key 0, ... 9 for the row. compile e. g. with gcc -Wall -O3 -pthread -o wifiscan wifiscan.c -lcurses or gcc -Wall -O3 -pthread -o wifiscan wifiscan.c -lncurses Dr. Rolf Freitag Licence: GPL (GNU PUBLIC LICENCE) V3. 2012-09-15 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // use common floating point numbers/arithmetic #include #include #include // simple version number #define SOFTWARE_VERSION_NUMBER 1.0 // Returns the number of elements in the array. // This is a standard way to calculate array length in C; it is described in K&R. #define mc_NUM_ELM(array) (sizeof array / sizeof 0[array]) // Returns the number of elements in the array. // minimum, maximum standard macro // In ANSI-C there is no need to cast (chapter 7.12.14.1 The isgreater macro etc.), but not-ANSI compilers like Keil // have a trap: At comparing signed and unsigned values the signed gets cast to unsigned without changing the bits! // The typeof operator can not be used here without declaring new variables. #define mc_MIN(a, b) ((a) < (b) ? (a) : (b)) #define mc_MAX(a, b) ((a) > (b) ? (a) : (b)) #define alpha 0.05 // constant for exponential smoothing, usually not higher than 0.1 and not lower than 0.01, 0.05 is a good value #define TIMEOUT 10 // for setting the "outdated, will soon be deleted flag", unit: Seconds #define DEADLINE 60 // for deleting outdated entries, unit: Seconds // global integer for input from stdin volatile int gi = 0; // structure for wifi data, with exponential smoothing: // Channel, encryption, ... struct s_wifi { int i_chan; int i_enc; long double ld_qual; long double ld_level; int i_max; int i_min; char ac_mac[18]; uint64_t u64_time; char ac_essid[64]; uint64_t u64_counter; char c_flag; }; // time in microseconds. Needs sys/time.h. uint64_t get_time (void) { static struct timeval ti; #ifndef __KERNEL__ static struct timezone tzp; gettimeofday (&ti, &tzp); #else do_gettimeofday (&ti); #endif return (ti.tv_usec + 1000000 * ((int64_t) ti.tv_sec)); // microseconds + time(NULL) * 1E6 } // global kbhit: Check if a key was pressed and put the value in the global integer as // a side effect. This function is non-blocking but takes about 200 ms when there is no // input. // Important: Termination via Ctrl-C does not work with gkbhit! But you can use a signal // handler to do this or check for the char 3 = Ctrl-C int gkbhit (void) { struct termios term, oterm; int fd = 0; int c = 0; tcgetattr (fd, &oterm); memcpy (&term, &oterm, sizeof (term)); term.c_lflag = term.c_lflag & (!ICANON); term.c_cc[VMIN] = 0; term.c_cc[VTIME] = 1; tcsetattr (fd, TCSANOW, &term); c = getchar (); if (c not_eq - 1) gi = c; // export the key pressed tcsetattr (fd, TCSANOW, &oterm); /* do not ungetc; the key pressed is read and exported as a side effect if (c != -1) ungetc (c, stdin); */ return ((c != -1) ? 1 : 0); // return 1 if a key was pressed } // Sorting functions ////////////////////////////////////////////// // qsort char comparison function int char_cmp (const void *a, const void *b) { const char *ia = (const char *) a; // casting pointer types const char *ib = (const char *) b; // returns negative if b > a and positive if a > b if (*ia == *ib) return 0; else return ((*ia > *ib) ? 1 : -1); } // char_cmp // qsort int comparison function int int_cmp (const void *a, const void *b) { const int *ia = (const int *) a; // casting pointer types const int *ib = (const int *) b; //return *ia - *ib; // returns negative if b > a and positive if a > b if (*ia == *ib) return 0; else return ((*ia > *ib) ? 1 : -1); } // int_cmp // qsort uint64 comparison function int uint64_cmp (const void *a, const void *b) { const uint64_t *ia = (const uint64_t *) a; // casting pointer types const uint64_t *ib = (const uint64_t *) b; // return negative if b > a and positive if a > b if (*ia == *ib) return 0; else return ((*ia > *ib) ? 1 : -1); } // uint64_cmp // qsort long double comparison function int ldouble_cmp (const void *a, const void *b) { const long double *ia = (const long double *) a; // casting pointer types const long double *ib = (const long double *) b; // return negative if b > a and positive if a > b if (*ia == *ib) return 0; else return ((*ia > *ib) ? 1 : -1); } // ldouble_cmp // qsort string comparison function int string_cmp (const void *a, const void *b) { const char *ia = (const char *) a; // casting pointer types const char *ib = (const char *) b; // return negative if b > a and positive if a > b return (strncmp (ia, ib, 123)); } // string_cmp // qsort comparison function for row 0 (Channel) int cmp0 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (int_cmp ((void *) &(ai->i_chan), (void *) &(bi->i_chan))); } // cmp0 // qsort comparison function for row 1 (Encryption) int cmp1 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (int_cmp ((void *) &(ai->i_enc), (void *) &(bi->i_enc))); } // cmp1 // qsort comparison function for row 2 (Quality) int cmp2 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (ldouble_cmp ((void *) &(ai->ld_qual), (void *) &(bi->ld_qual))); } // cmp2 // qsort comparison function for row 3 (Level) int cmp3 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (ldouble_cmp ((void *) &(ai->ld_level), (void *) &(bi->ld_level))); } // cmp3 // qsort comparison function for row 4 (max level) int cmp4 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (int_cmp ((void *) &(ai->i_max), (void *) &(bi->i_max))); } // cmp4 // qsort comparison function for row 5 (min level) int cmp5 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (int_cmp ((void *) &(ai->i_min), (void *) &(bi->i_min))); } // cmp5 // qsort comparison function for row 6 (MAC) int cmp6 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (string_cmp ((void *) &(ai->ac_mac), (void *) &(bi->ac_mac))); } // cmp6 // qsort comparison function for row 7 (time) int cmp7 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (uint64_cmp ((void *) &(ai->u64_time), (void *) &(bi->u64_time))); } // cmp7 // qsort comparison function for row 8 (flag) int cmp8 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (char_cmp ((void *) &(ai->c_flag), (void *) &(bi->c_flag))); } // cmp9 // qsort comparison function for row 9 (ESSID) int cmp9 (const void *a, const void *b) { const struct s_wifi *ai = (const struct s_wifi *) a; const struct s_wifi *bi = (const struct s_wifi *) b; return (string_cmp ((void *) &(ai->ac_essid), (void *) &(bi->ac_essid))); } // cmp8 /*---------------------------------------------------------------------*/ // Thread for reading the keys, for termination or sorting void * read_key (void *threadid) { __fpurge (stdin); // flush the buffer // while a quit key is not pressed for (; (gi not_eq ((int) 'q')) and (gi not_eq ((int) 'Q') and (gi not_eq 3));) { gkbhit (); // get the key pressed as a side effect } sleep (1); // wait a second (for the main thread) nocbreak (); // curses call to set waiting for Enter key echo (); // curses call to set echoing (void) curs_set (1); // visible cursor endwin (); // end curses (void) curs_set (1); // restore, visible cursor on the command line exit (0); // also terminate the main thread //pthread_exit (NULL); // terminates the calling thread and to join with the terminating thread. } /*---------------------------------------------------------------------*/ int main (int argc, char **argv) { int i = 0, j = 0, i_ret = 0, id = 0, i_run = 1, i_size; int64_t i64 = 0; char c_sort = 0; static pthread_t thread; DIR *dirHandle; struct dirent *dirEntry; FILE *ifp = NULL; // input file pointer char c = 0; static char line[12345]; // line static char str0[1234]; // string0 static char ac_enc[1234]; // encryption string (on/off) static struct s_wifi as_wifidata[1234]; // the stored wifi data static struct s_wifi s_wifidat; // actual wifi data, scanned from the input file; empty struct static struct s_wifi s_wifiempty; // empty struct int i_q0 = 0, i_q1 = 0, i_l = 0; // zeroth integer of quality, first integer of quality, integer of level char ac_string[1234]; // file name if ((argc > 1) and not strncmp (argv[1], "-V", 123)) { (void) fprintf (stdout, "%s version %.2f\n", argv[0], SOFTWARE_VERSION_NUMBER); (void) fprintf (stdout, "Usage: Without option or with -V for only displaying the version.\n"); (void) fprintf (stdout, "Hit 0, 1, 2, ... for sorting by column 0, 1, 2, ..., and q for exit.\n"); (void) fprintf (stdout, "This program needs a freewifi_autoconnect script running for input.\n"); (void) fprintf (stdout, "If the terminal does not work as usual after program terminatio, enter reset.\n"); exit ((2 == argc) ? 0 : -1); } // create thread for timeout (to avoid hangup in open_serial, e. g. when the device can not be opened) i_ret = pthread_create (&thread, NULL, read_key, (void *) &id); if (i_ret) { (void) fprintf (stderr, "ERROR; return code from pthread_create() is %d\n", i_ret); exit (-1); } initscr (); // curses call to initialize window cbreak (); // curses call to set no waiting for Enter key noecho (); // curses call to set no echoing fflush (stdout); __fpurge (stdout); // flush the buffer __fpurge (stderr); // flush the buffer refresh (); // curses call to implement all changes since last refresh (void) curs_set (0); // invisible cursor // check the last key pressed; while a quit key is not pressed for (; (gi not_eq ((int) 'q')) and (gi not_eq ((int) 'Q') and (gi not_eq 3));) { // Key evaluation switch (gi) { case 9: // tab: next row ++c_sort; c_sort %= 10; break; case 0x25: // left arraw: row left if (c_sort) --c_sort; else c_sort = 9; break; case 0x27: // right arraw: row right ++c_sort; c_sort %= 10; break; default: // no (valid) control key c_sort = (char) gi; // store the key pressed break; } // Key Mapping: Digits are not changed, some chars are mapped to a digit if ('c' == c_sort) // Channel c_sort = '0'; if ('e' == c_sort) // Encryption c_sort = '1'; if ('u' == c_sort) // qUality c_sort = '2'; if ('l' == c_sort) // Level c_sort = '3'; if ('m' == c_sort) // Max level c_sort = '4'; if ('n' == c_sort) // MiN level c_sort = '5'; if ('h' == c_sort) // mac, Hardware address c_sort = '6'; if ('t' == c_sort) // Time c_sort = '7'; if ('s' == c_sort) // eSsid c_sort = '8'; if ('f' == c_sort) // Flag c_sort = '9'; // first part: Scan for files (with wifi data), store the file content, delete the files to avoid double reading dirHandle = opendir ("/tmp/wifiscan/"); if (dirHandle) { i_run = 0; while (0 != (dirEntry = readdir (dirHandle))) { if (8 == dirEntry->d_type) // if we have a normal file (no directory, no symlink, ...) { i_run++; memset (ac_string, 0, sizeof (ac_string)); snprintf (ac_string, sizeof (ac_string) - 1, "%s/%s", "/tmp/wifiscan", dirEntry->d_name); // file name with path ifp = fopen (ac_string, "r"); if ((NULL == ifp) or (NULL == fgets (line, sizeof (line), ifp))) { fprintf (stderr, "\n Could really not open file %s.\n", ac_string); } else { // TODO: Scan version which also works with whitespaces in the ESSID i = sscanf (line, "%d %s %d/%d %d %s %llu %s", &(s_wifidat.i_chan), ac_enc, &i_q0, &i_q1, &i_l, s_wifidat.ac_mac, (long long unsigned int *)&(s_wifidat.u64_time), s_wifidat.ac_essid); if (strncmp (ac_enc, "off", sizeof (ac_enc)) == 0) // if encryption is off s_wifidat.i_enc = 0; else s_wifidat.i_enc = 1; s_wifidat.ld_qual = (long double) i_q0; s_wifidat.ld_qual /= (long double) i_q1; s_wifidat.ld_level = (long double) i_l; s_wifidat.i_min = i_l; s_wifidat.i_max = i_l; fclose (ifp); ifp = NULL; if (i != 8) // no all arguments found { fprintf (stderr, "Only %d arguments found, run %d, file %s\n", i, i_run, ac_string); fprintf (stderr, "Input string: %s\n", line); fprintf (stderr, "Scanned: %d;%s;%d/%d;%d;%s;%llu;%s\n", s_wifidat.i_chan, ac_enc, i_q0, i_q1, i_l, s_wifidat.ac_mac, (long long unsigned int)s_wifidat.u64_time, s_wifidat.ac_essid); } else // file ok and finished: Erase (overwrite, rename, truncate, remove) { ifp = fopen (ac_string, "w+b"); if (ifp not_eq NULL) { fseek (ifp, 0L, SEEK_END); // stream to the file end i_size = ftell (ifp); // size in byte c = 0; fwrite (&c, 1, i_size, ifp); // overwrite fclose (ifp); ifp = NULL; } rename (ac_string, "/tmp/wifiscan/foobar"); i = truncate ("/tmp/wifiscan/foobar", 0); // set length zero remove ("/tmp/wifiscan/foobar"); // delete // now we have the wifi data, so the data base (array of wifi data) has to be updated // first part of the update: check if the WiFi is known //i_found = -1; // sentinel: -1 for not found j = mc_NUM_ELM (as_wifidata); for (i = 0; i < j; i++) { if ( // (strncmp (s_wifidat.ac_mac, as_wifidata[i].ac_mac, sizeof (s_wifidat.ac_mac)) == 0) // equal MAC and (strncmp (s_wifidat.ac_essid, as_wifidata[i].ac_essid, sizeof (s_wifidat.ac_essid)) == 0) // equal ESSID and (s_wifidat.i_chan == as_wifidata[i].i_chan) // equal channel ) // equal MAC and ESSID and channel break; // found, end of looping/searching } // for i if (i < j) // if the wifi is known: update { // update: MAC and ESSID and channel are equal, so udate only encryption, quality, level, min, max, (last) time, flag as_wifidata[i].i_enc = s_wifidat.i_enc; as_wifidata[i].ld_qual *= (1.0 - alpha); as_wifidata[i].ld_qual += alpha * s_wifidat.ld_qual; as_wifidata[i].ld_level *= (1.0 - alpha); as_wifidata[i].ld_level += alpha * s_wifidat.ld_level; as_wifidata[i].i_min = mc_MIN (as_wifidata[i].i_min, s_wifidat.i_min); as_wifidata[i].i_max = mc_MAX (as_wifidata[i].i_max, s_wifidat.i_max); as_wifidata[i].u64_time = s_wifidat.u64_time; memcpy (as_wifidata[i].ac_essid, s_wifidat.ac_essid, sizeof (s_wifidat.ac_essid)); as_wifidata[i].u64_counter++; } else // not known: insert { // insertion: look for an empty array entry j = mc_NUM_ELM (as_wifidata); for (i = 0; i < j; i++) { if (0 == memcmp (&(as_wifidata[i]), &(s_wifiempty), sizeof (s_wifiempty))) // if empty { memcpy (&(as_wifidata[i]), &(s_wifidat), sizeof (s_wifidat)); // store as_wifidata[i].u64_counter = 1; as_wifidata[i].c_flag = '+'; // new flag // insert other data: flag: TODO // as_wifidata[i].c_flag= // fprintf (stderr, "inserted %s\n", as_wifidata[i].ac_mac); break; } } // for i if (i >= j) // array full; no space { fprintf (stderr, "WiFi data base is full; can not store the data of the new WiFi %s found.\n", s_wifidat.ac_essid); } } // else; insertion } // else, file ok } // else, ifp if (NULL != ifp) fclose (ifp); } // if (8 === } // while (0 != closedir (dirHandle); } // if (dirhandle) // second part: Delete outdates entries, older than 15 seconds, set or clear the flag for (i = 0; i < mc_NUM_ELM (as_wifidata); i++) { if (as_wifidata[i].i_chan) // not empty, no empty data { i64 = (get_time () / 1000 - (int64_t) as_wifidata[i].u64_time) / 1000; // time difference in (truncated) seconds if (('+' == as_wifidata[i].c_flag) and (10 == as_wifidata[i].u64_counter)) // at the tenth update: Clear the new Flag as_wifidata[i].c_flag = 0; if ((i64 > TIMEOUT) and (as_wifidata[i].u64_counter > 1)) // more than 10 seconds not seen, set the "soon timeout flag" as_wifidata[i].c_flag = 'O'; else // no soon timeout { if (as_wifidata[i].u64_counter >= TIMEOUT) // not new as_wifidata[i].c_flag = 0; // no soon timeout, not new else as_wifidata[i].c_flag = '+'; // new } if (i64 > DEADLINE) // more than 20 seconds not seen: Delete memset (&(as_wifidata[i]), 0, sizeof (s_wifiempty)); } } // third part: Sort the data base (array of wifi data) switch (c_sort) { default: case '0': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp0); break; case '1': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp1); break; case '2': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp2); break; case '3': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp3); break; case '4': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp4); break; case '5': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp5); break; case '6': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp6); break; case '7': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp7); break; case '8': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp8); break; case '9': qsort (as_wifidata, mc_NUM_ELM (as_wifidata), sizeof (as_wifidata[0]), cmp9); break; } // switch // fourth part: Print the data clear (); // curses call to clear screen, send cursor to position (0,0) refresh (); // curses call to implement all changes since last refresh fprintf (stdout, "Ch. Enc. Qual. Level MAX MIN MAC t f count ESSID"); for (i = 0; i < mc_NUM_ELM (as_wifidata); i++) { if (as_wifidata[i].i_chan) // not empty, no empty data { str0[0] = as_wifidata[i].c_flag; str0[1] = 0; (void) fprintf (stdout, "\n\r%2d %2d %.3Lf %.2Lf %3d %3d %s %2lld %1s %6llx %s", as_wifidata[i].i_chan, as_wifidata[i].i_enc, as_wifidata[i].ld_qual, as_wifidata[i].ld_level, as_wifidata[i].i_max, as_wifidata[i].i_min, as_wifidata[i].ac_mac, (long long unsigned int)(get_time () / 1000 - (int64_t) as_wifidata[i].u64_time) / 1000, str0, (long long unsigned int)as_wifidata[i].u64_counter, as_wifidata[i].ac_essid); } } // for i fflush (stdout); __fpurge (stdout); // flush the buffer refresh (); // fifth part: Sleep, wait for next round sleep (1); // wait a second before the next run } // for, mainloop // end part nocbreak (); // curses call to set waiting for Enter key echo (); // curses call to set echoing (void) curs_set (1); // visible cursor endwin (); // end curses pthread_kill (thread, 15); // terminate thread with signal 15 pthread_exit (NULL); //system ("reset"); exit (0); } // main