A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1091 lines 31 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2016 Franklin Wei 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21 22/* simple OTP plugin */ 23 24/* see RFCs 4226, 6238 for more information about the algorithms used */ 25 26#include "plugin.h" 27 28#include "lib/display_text.h" 29#include "lib/pluginlib_actions.h" 30#include "lib/pluginlib_exit.h" 31#include "lib/sha1.h" 32 33#define MAX_NAME 50 34#define SECRET_MAX 256 35#define URI_MAX 256 36#define ACCT_FILE PLUGIN_APPS_DATA_DIR "/otp.dat" 37 38#define MAX(a, b) (((a)>(b))?(a):(b)) 39 40struct account_t { 41 char name[MAX_NAME]; 42 43 bool is_totp; // hotp otherwise 44 45 union { 46 uint64_t hotp_counter; 47 unsigned long totp_period; 48 }; 49 50 int digits; 51 52 unsigned char secret[SECRET_MAX]; 53 int sec_len; 54}; 55 56static int max_accts = 0; 57 58/* in plugin buffer */ 59static struct account_t *accounts = NULL; 60 61static int next_slot = 0; 62 63/* in SECONDS, asked for on first run */ 64static int time_offs = 0; 65 66static int HOTP(unsigned char *secret, size_t sec_len, uint64_t ctr, int digits) 67{ 68 ctr = htobe64(ctr); 69 unsigned char hash[20]; 70 if(hmac_sha1(secret, sec_len, &ctr, 8, hash)) 71 { 72 return -1; 73 } 74 75 int offs = hash[19] & 0xF; 76 uint32_t code = (hash[offs] & 0x7F) << 24 | 77 hash[offs + 1] << 16 | 78 hash[offs + 2] << 8 | 79 hash[offs + 3]; 80 81 int mod = 1; 82 for(int i = 0; i < digits; ++i) 83 mod *= 10; 84 85 // debug 86 // rb->splashf(HZ * 5, "HOTP %*s, %llu, %d: %d", sec_len, secret, htobe64(ctr), digits, code % mod); 87 88 return code % mod; 89} 90 91#if CONFIG_RTC 92static time_t get_utc(void) 93{ 94 return rb->mktime(rb->get_time()) - time_offs; 95} 96 97static int TOTP(unsigned char *secret, size_t sec_len, uint64_t step, int digits) 98{ 99 uint64_t tm = get_utc() / step; 100 return HOTP(secret, sec_len, tm, digits); 101} 102#endif 103 104/* search the accounts for a duplicate */ 105static bool acct_exists(const char *name) 106{ 107 for(int i = 0; i < next_slot; ++i) 108 if(!rb->strcmp(accounts[i].name, name)) 109 return true; 110 return false; 111} 112 113// Base32 implementation 114// 115// Copyright 2010 Google Inc. 116// Author: Markus Gutschke 117// 118// Licensed under the Apache License, Version 2.0 (the "License"); 119// you may not use this file except in compliance with the License. 120// You may obtain a copy of the License at 121// 122// http://www.apache.org/licenses/LICENSE-2.0 123// 124// Unless required by applicable law or agreed to in writing, software 125// distributed under the License is distributed on an "AS IS" BASIS, 126// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 127// See the License for the specific language governing permissions and 128// limitations under the License. 129 130static int base32_decode(uint8_t *result, int bufSize, const uint8_t *encoded) { 131 int buffer = 0; 132 int bitsLeft = 0; 133 int count = 0; 134 for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { 135 uint8_t ch = *ptr; 136 if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { 137 continue; 138 } 139 buffer <<= 5; 140 141 // Deal with commonly mistyped characters 142 if (ch == '0') { 143 ch = 'O'; 144 } else if (ch == '1') { 145 ch = 'L'; 146 } else if (ch == '8') { 147 ch = 'B'; 148 } 149 150 // Look up one base32 digit 151 if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { 152 ch = (ch & 0x1F) - 1; 153 } else if (ch >= '2' && ch <= '7') { 154 ch -= '2' - 26; 155 } else { 156 return -1; 157 } 158 159 buffer |= ch; 160 bitsLeft += 5; 161 if (bitsLeft >= 8) { 162 result[count++] = buffer >> (bitsLeft - 8); 163 bitsLeft -= 8; 164 } 165 } 166 if (count < bufSize) { 167 result[count] = '\000'; 168 } 169 return count; 170} 171 172static int base32_encode(const uint8_t *data, int length, uint8_t *result, 173 int bufSize) { 174 if (length < 0 || length > (1 << 28)) { 175 return -1; 176 } 177 int count = 0; 178 if (length > 0) { 179 int buffer = data[0]; 180 int next = 1; 181 int bitsLeft = 8; 182 while (count < bufSize && (bitsLeft > 0 || next < length)) { 183 if (bitsLeft < 5) { 184 if (next < length) { 185 buffer <<= 8; 186 buffer |= data[next++] & 0xFF; 187 bitsLeft += 8; 188 } else { 189 int pad = 5 - bitsLeft; 190 buffer <<= pad; 191 bitsLeft += pad; 192 } 193 } 194 int index = 0x1F & (buffer >> (bitsLeft - 5)); 195 bitsLeft -= 5; 196 result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; 197 } 198 } 199 if (count < bufSize) { 200 result[count] = '\000'; 201 } 202 return count; 203} 204 205/*********************************************************************** 206 * File browser (from rockpaint) 207 ***********************************************************************/ 208 209static bool browse( char *dst, int dst_size, const char *start ) 210{ 211 struct browse_context browse = { 212 .dirfilter = SHOW_ALL, 213 .flags = BROWSE_SELECTONLY | BROWSE_NO_CONTEXT_MENU, 214 .icon = Icon_NOICON, 215 .root = start, 216 .buf = dst, 217 .bufsize = dst_size, 218 }; 219 220 rb->rockbox_browse(&browse); 221 return (browse.flags & BROWSE_SELECTED); 222} 223 224static bool read_accts(void) 225{ 226 int fd = rb->open(ACCT_FILE, O_RDONLY); 227 if(fd < 0) 228 return false; 229 230 char buf[4]; 231 char magic[4] = { 'O', 'T', 'P', '1' }; 232 rb->read(fd, buf, 4); 233 if(memcmp(magic, buf, 4)) 234 { 235 rb->splash(HZ * 2, "Corrupt save data!"); 236 rb->close(fd); 237 return false; 238 } 239 240 rb->read(fd, &time_offs, sizeof(time_offs)); 241 242 while(next_slot < max_accts) 243 { 244 if(rb->read(fd, accounts + next_slot, sizeof(struct account_t)) != sizeof(struct account_t)) 245 break; 246 ++next_slot; 247 } 248 249 rb->close(fd); 250 return true; 251} 252 253static void save_accts(void) 254{ 255 int fd = rb->open(ACCT_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0600); 256 rb->fdprintf(fd, "OTP1"); 257 258 rb->write(fd, &time_offs, sizeof(time_offs)); 259 260 for(int i = 0; i < next_slot; ++i) 261 rb->write(fd, accounts + i, sizeof(struct account_t)); 262 rb->close(fd); 263} 264 265static void add_acct_file(void) 266{ 267 char fname[MAX_PATH]; 268 rb->splash(HZ * 2, "Please choose file containing URI(s)."); 269 int before = next_slot; 270 if(browse(fname, sizeof(fname), "/")) 271 { 272 int fd = rb->open(fname, O_RDONLY); 273 do { 274 memset(accounts + next_slot, 0, sizeof(struct account_t)); 275 276 accounts[next_slot].digits = 6; 277 278 char uri_buf[URI_MAX]; 279 if(!rb->read_line(fd, uri_buf, sizeof(uri_buf))) 280 break; 281 282 if(next_slot >= max_accts) 283 { 284 rb->splash(HZ * 2, "Account limit reached: some accounts not added."); 285 break; 286 } 287 288 /* check for URI prefix */ 289 if(rb->strncmp(uri_buf, "otpauth://", 10)) 290 continue; 291 292 char *save; 293 char *tok = rb->strtok_r(uri_buf + 10, "/", &save); 294 if(!rb->strcmp(tok, "totp")) 295 { 296 accounts[next_slot].is_totp = true; 297 accounts[next_slot].totp_period = 30; 298#if !CONFIG_RTC 299 rb->splash(2 * HZ, "TOTP not supported!"); 300 continue; 301#endif 302 } 303 else if(!rb->strcmp(tok, "hotp")) 304 { 305 accounts[next_slot].is_totp = false; 306 accounts[next_slot].hotp_counter = 0; 307 } 308 309 tok = rb->strtok_r(NULL, "?", &save); 310 if(!tok) 311 continue; 312 313 if(acct_exists(tok)) 314 { 315 rb->splashf(HZ * 2, "Not adding account with duplicate name `%s'!", tok); 316 continue; 317 } 318 319 if(!rb->strlen(tok)) 320 { 321 rb->splashf(HZ * 2, "Skipping account with empty name."); 322 continue; 323 } 324 325 rb->strlcpy(accounts[next_slot].name, tok, sizeof(accounts[next_slot].name)); 326 327 bool have_secret = false; 328 329 do { 330 tok = rb->strtok_r(NULL, "=", &save); 331 if(!tok) 332 continue; 333 334 if(!rb->strcmp(tok, "secret")) 335 { 336 if(have_secret) 337 { 338 rb->splashf(HZ * 2, "URI with multiple `secret' parameters found, skipping!"); 339 goto fail; 340 } 341 have_secret = true; 342 tok = rb->strtok_r(NULL, "&", &save); 343 if((accounts[next_slot].sec_len = base32_decode(accounts[next_slot].secret, SECRET_MAX, tok)) <= 0) 344 goto fail; 345 } 346 else if(!rb->strcmp(tok, "counter")) 347 { 348 if(accounts[next_slot].is_totp) 349 { 350 rb->splash(HZ * 2, "Counter parameter specified for TOTP!? Skipping..."); 351 goto fail; 352 } 353 tok = rb->strtok_r(NULL, "&", &save); 354 accounts[next_slot].hotp_counter = rb->atoi(tok); 355 } 356 else if(!rb->strcmp(tok, "period")) 357 { 358 if(!accounts[next_slot].is_totp) 359 { 360 rb->splash(HZ * 2, "Period parameter specified for HOTP!? Skipping..."); 361 goto fail; 362 } 363 tok = rb->strtok_r(NULL, "&", &save); 364 accounts[next_slot].totp_period = rb->atoi(tok); 365 } 366 else if(!rb->strcmp(tok, "digits")) 367 { 368 tok = rb->strtok_r(NULL, "&", &save); 369 accounts[next_slot].digits = rb->atoi(tok); 370 if(accounts[next_slot].digits < 1 || accounts[next_slot].digits > 9) 371 { 372 rb->splashf(HZ * 2, "Digits parameter not in acceptable range, skipping."); 373 goto fail; 374 } 375 } 376 else 377 rb->splashf(HZ, "Unnown parameter `%s' ignored.", tok); 378 } while(tok); 379 380 if(!have_secret) 381 { 382 rb->splashf(HZ * 2, "URI with NO `secret' parameter found, skipping!"); 383 goto fail; 384 } 385 386 ++next_slot; 387 388 fail: 389 390 ; 391 } while(1); 392 rb->close(fd); 393 } 394 if(before == next_slot) 395 rb->splash(HZ * 2, "No accounts added."); 396 else 397 { 398 rb->splashf(HZ * 2, "Added %d account(s).", next_slot - before); 399 save_accts(); 400 } 401} 402 403static void add_acct_manual(void) 404{ 405 if(next_slot >= max_accts) 406 { 407 rb->splashf(HZ * 2, "Account limit reached!"); 408 return; 409 } 410 memset(accounts + next_slot, 0, sizeof(struct account_t)); 411 412 rb->splash(HZ * 1, "Enter account name."); 413 char* buf = accounts[next_slot].name; 414 if(rb->kbd_input(buf, sizeof(accounts[next_slot].name), NULL) < 0) 415 return; 416 417 if(acct_exists(buf)) 418 { 419 rb->splash(HZ * 2, "Duplicate account name!"); 420 return; 421 } 422 423 rb->splash(HZ * 2, "Enter base32-encoded secret."); 424 425 char temp_buf[SECRET_MAX * 2]; 426 memset(temp_buf, 0, sizeof(temp_buf)); 427 428 if(rb->kbd_input(temp_buf, sizeof(temp_buf), NULL) < 0) 429 return; 430 431 if((accounts[next_slot].sec_len = base32_decode(accounts[next_slot].secret, SECRET_MAX, temp_buf)) <= 0) 432 { 433 rb->splash(HZ * 2, "Invalid Base32 secret!"); 434 return; 435 } 436 437#if CONFIG_RTC 438 const struct text_message prompt = { (const char*[]) {"Is this a TOTP account?", "The protocol can be determined from the URI."}, 2}; 439 enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL); 440 if(response == YESNO_NO) 441 accounts[next_slot].is_totp = false; 442 else 443 accounts[next_slot].is_totp = true; 444#endif 445 446 memset(temp_buf, 0, sizeof(temp_buf)); 447 448 if(!accounts[next_slot].is_totp) 449 { 450 rb->splash(HZ * 2, "Enter counter (0 is normal)."); 451 temp_buf[0] = '0'; 452 } 453 else 454 { 455 rb->splash(HZ * 2, "Enter time step (30 is normal)."); 456 temp_buf[0] = '3'; 457 temp_buf[1] = '0'; 458 } 459 460 if(rb->kbd_input(temp_buf, sizeof(temp_buf), NULL) < 0) 461 return; 462 463 if(!accounts[next_slot].is_totp) 464 accounts[next_slot].hotp_counter = rb->atoi(temp_buf); 465 else 466 accounts[next_slot].totp_period = rb->atoi(temp_buf); 467 468 rb->splash(HZ * 2, "Enter code length (6 is normal)."); 469 470 memset(temp_buf, 0, sizeof(temp_buf)); 471 temp_buf[0] = '6'; 472 473 if(rb->kbd_input(temp_buf, sizeof(temp_buf), NULL) < 0) 474 return; 475 476 accounts[next_slot].digits = rb->atoi(temp_buf); 477 478 if(accounts[next_slot].digits < 1 || accounts[next_slot].digits > 9) 479 { 480 rb->splash(HZ, "Invalid length!"); 481 return; 482 } 483 484 ++next_slot; 485 486 save_accts(); 487 488 rb->splashf(HZ * 2, "Success."); 489} 490 491static void add_acct(void) 492{ 493 MENUITEM_STRINGLIST(menu, "Add Account", NULL, 494 "From URI on disk", 495 "Manual Entry", 496 "Back"); 497 int sel = 0; 498 bool quit = false; 499 while(!quit) 500 { 501 switch(rb->do_menu(&menu, &sel, NULL, false)) 502 { 503 case 0: 504 add_acct_file(); 505 break; 506 case 1: 507 add_acct_manual(); 508 break; 509 case 2: 510 default: 511 quit = true; 512 break; 513 } 514 } 515} 516 517static void show_code(int acct) 518{ 519 if(!accounts[acct].is_totp) 520 { 521 rb->splashf(0, "%0*d", accounts[acct].digits, 522 HOTP(accounts[acct].secret, 523 accounts[acct].sec_len, 524 accounts[acct].hotp_counter, 525 accounts[acct].digits)); 526 ++accounts[acct].hotp_counter; 527 } 528#if CONFIG_RTC 529 else 530 { 531 rb->splashf(0, "%0*d (%lu seconds(s) left)", accounts[acct].digits, 532 TOTP(accounts[acct].secret, 533 accounts[acct].sec_len, 534 accounts[acct].totp_period, 535 accounts[acct].digits), 536 accounts[acct].totp_period - (unsigned long) get_utc() % accounts[acct].totp_period); 537 } 538#else 539 else 540 { 541 rb->splash(0, "TOTP not supported on this device!"); 542 } 543#endif 544 rb->sleep(HZ * 2); 545 while(1) 546 { 547 int button = rb->button_get(true); 548 if(button && !(button & BUTTON_REL)) 549 break; 550 rb->yield(); 551 } 552 553 save_accts(); 554 rb->lcd_clear_display(); 555} 556 557static void gen_codes(void) 558{ 559 rb->lcd_clear_display(); 560 /* native menus don't seem to support dynamic names easily, so we 561 * roll our own */ 562 static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; 563 int idx = 0; 564 if(next_slot > 0) 565 { 566 rb->lcd_putsf(0, 0, "Generate Code"); 567 rb->lcd_putsf(0, 1, "%s", accounts[0].name); 568 rb->lcd_update(); 569 } 570 else 571 { 572 rb->splash(HZ * 2, "No accounts configured!"); 573 return; 574 } 575 while(1) 576 { 577 int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts)); 578 switch(button) 579 { 580 case PLA_LEFT: 581 --idx; 582 if(idx < 0) 583 idx = next_slot - 1; 584 break; 585 case PLA_RIGHT: 586 ++idx; 587 if(idx >= next_slot) 588 idx = 0; 589 break; 590 case PLA_SELECT: 591 show_code(idx); 592 break; 593 case PLA_CANCEL: 594 case PLA_EXIT: 595 exit_on_usb(button); 596 return; 597 default: 598 break; 599 } 600 rb->lcd_clear_display(); 601 rb->lcd_putsf(0, 0, "Generate Code"); 602 rb->lcd_putsf(0, 1, "%s", accounts[idx].name); 603 rb->lcd_update(); 604 } 605} 606 607static bool danger_confirm(void) 608{ 609 int sel = 0; 610 MENUITEM_STRINGLIST(menu, "Are you REALLY SURE?", NULL, 611 "No", 612 "No", 613 "No", 614 "No", 615 "No", 616 "No", 617 "No", 618 "Yes, DO IT", // 7 619 "No", 620 "No", 621 "No", 622 "No"); 623 624 switch(rb->do_menu(&menu, &sel, NULL, false)) 625 { 626 case 7: 627 return true; 628 default: 629 return false; 630 } 631} 632 633char data_buf[MAX(MAX_NAME, SECRET_MAX * 2)]; 634char temp_sec[SECRET_MAX]; 635size_t old_len; 636 637static void edit_menu(int acct) 638{ 639 rb->splashf(HZ, "Editing account `%s'.", accounts[acct].name); 640 641 /* HACK ALERT */ 642 /* two different menus, one handling logic */ 643 MENUITEM_STRINGLIST(menu_1, "Edit Account", NULL, 644 "Rename", 645 "Delete", 646 "Change HOTP Counter", 647 "Change Digit Count", 648 "Change Shared Secret", 649 "Back"); 650 651 MENUITEM_STRINGLIST(menu_2, "Edit Account", NULL, 652 "Rename", // 0 653 "Delete", // 1 654 "Change TOTP Period", // 2 655 "Change Digit Count", // 3 656 "Change Shared Secret", // 4 657 "Back"); // 5 658 659 const struct menu_item_ex *menu = (accounts[acct].is_totp) ? &menu_2 : &menu_1; 660 661 bool quit = false; 662 int sel = 0; 663 while(!quit) 664 { 665 switch(rb->do_menu(menu, &sel, NULL, false)) 666 { 667 case 0: // rename 668 rb->splash(HZ, "Enter new name."); 669 rb->strlcpy(data_buf, accounts[acct].name, sizeof(data_buf)); 670 if(rb->kbd_input(data_buf, sizeof(data_buf), NULL) < 0) 671 break; 672 if(acct_exists(data_buf)) 673 { 674 rb->splash(HZ * 2, "Duplicate account name!"); 675 break; 676 } 677 rb->strlcpy(accounts[acct].name, data_buf, sizeof(accounts[acct].name)); 678 save_accts(); 679 break; 680 case 1: // delete 681 if(danger_confirm()) 682 { 683 rb->memmove(accounts + acct, accounts + acct + 1, (next_slot - acct - 1) * sizeof(struct account_t)); 684 --next_slot; 685 save_accts(); 686 rb->splashf(HZ, "Deleted."); 687 return; 688 } 689 else 690 rb->splash(HZ, "Not confirmed."); 691 break; 692 case 2: // HOTP counter OR TOTP period 693 if(accounts[acct].is_totp) 694 rb->snprintf(data_buf, sizeof(data_buf), "%d", (int)accounts[acct].hotp_counter); 695 else 696 rb->snprintf(data_buf, sizeof(data_buf), "%lu", accounts[acct].totp_period); 697 698 if(rb->kbd_input(data_buf, sizeof(data_buf), NULL) < 0) 699 break; 700 701 if(accounts[acct].is_totp) 702 accounts[acct].totp_period = rb->atoi(data_buf); 703 else 704 accounts[acct].hotp_counter = rb->atoi(data_buf); 705 706 save_accts(); 707 708 rb->splash(HZ, "Success."); 709 break; 710 case 3: // digits 711 rb->snprintf(data_buf, sizeof(data_buf), "%d", accounts[acct].digits); 712 if(rb->kbd_input(data_buf, sizeof(data_buf), NULL) < 0) 713 break; 714 715 accounts[acct].digits = rb->atoi(data_buf); 716 save_accts(); 717 718 rb->splash(HZ, "Success."); 719 break; 720 case 4: // secret 721 old_len = accounts[acct].sec_len; 722 memcpy(temp_sec, accounts[acct].secret, accounts[acct].sec_len); 723 base32_encode(accounts[acct].secret, accounts[acct].sec_len, data_buf, sizeof(data_buf)); 724 725 if(rb->kbd_input(data_buf, sizeof(data_buf), NULL) < 0) 726 break; 727 728 int ret = base32_decode(accounts[acct].secret, sizeof(accounts[acct].secret), data_buf); 729 if(ret <= 0) 730 { 731 memcpy(accounts[acct].secret, temp_sec, SECRET_MAX); 732 accounts[acct].sec_len = old_len; 733 rb->splash(HZ * 2, "Invalid Base32 secret!"); 734 break; 735 } 736 accounts[acct].sec_len = ret; 737 738 save_accts(); 739 rb->splash(HZ, "Success."); 740 break; 741 case 5: 742 quit = true; 743 break; 744 default: 745 break; 746 } 747 } 748} 749 750static void edit_accts(void) 751{ 752 rb->lcd_clear_display(); 753 /* native menus don't seem to support dynamic names easily, so we 754 * roll our own */ 755 static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; 756 int idx = 0; 757 if(next_slot > 0) 758 { 759 rb->lcd_putsf(0, 0, "Edit Account"); 760 rb->lcd_putsf(0, 1, "%s", accounts[0].name); 761 rb->lcd_update(); 762 } 763 else 764 { 765 rb->splash(HZ * 2, "No accounts configured!"); 766 return; 767 } 768 while(1) 769 { 770 int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts)); 771 switch(button) 772 { 773 case PLA_LEFT: 774 --idx; 775 if(idx < 0) 776 idx = next_slot - 1; 777 break; 778 case PLA_RIGHT: 779 ++idx; 780 if(idx >= next_slot) 781 idx = 0; 782 break; 783 case PLA_SELECT: 784 edit_menu(idx); 785 if(!next_slot) 786 return; 787 if(idx == next_slot) 788 idx = 0; 789 break; 790 case PLA_CANCEL: 791 case PLA_EXIT: 792 return; 793 default: 794 exit_on_usb(button); 795 break; 796 } 797 rb->lcd_clear_display(); 798 rb->lcd_putsf(0, 0, "Edit Account"); 799 rb->lcd_putsf(0, 1, "%s", accounts[idx].name); 800 rb->lcd_update(); 801 } 802} 803 804#if CONFIG_RTC 805 806/* label is like this: [+/-]HH:MM ... */ 807static int get_time_seconds(const char *label) 808{ 809 if(!rb->strcmp(label, "UTC")) 810 return 0; 811 812 char buf[32]; 813 814 /* copy the part after "UTC" */ 815 rb->strlcpy(buf, label + 3, sizeof(buf)); 816 817 char *save, *tok; 818 819 tok = rb->strtok_r(buf, ":", &save); 820 /* positive or negative: sign left */ 821 int hr = rb->atoi(tok); 822 823 tok = rb->strtok_r(NULL, ": ", &save); 824 int min = rb->atoi(tok); 825 826 return 3600 * hr + 60 * min; 827} 828 829/* returns the offset in seconds associated with a time zone */ 830static int get_time_offs(void) 831{ 832 MENUITEM_STRINGLIST(menu, "Select Time Offset", NULL, 833 "UTC-12:00", // 0 834 "UTC-11:00", // 1 835 "UTC-10:00 (HAST)", // 2 836 "UTC-9:30", // 3 837 "UTC-9:00 (AKST, HADT)", // 4 838 "UTC-8:00 (PST, AKDT)", // 5 839 "UTC-7:00 (MST, PDT)", // 6 840 "UTC-6:00 (CST, MDT)", // 7 841 "UTC-5:00 (EST, CDT)", // 8 842 "UTC-4:00 (AST, EDT)", // 9 843 "UTC-3:30 (NST)", // 10 844 "UTC-3:00 (ADT)", // 11 845 "UTC-2:30 (NDT)", // 12 846 "UTC-2:00", // 13 847 "UTC-1:00", // 14 848 "UTC", // 15 849 "UTC+1:00", // 16 850 "UTC+2:00", // 17 851 "UTC+3:00", // 18 852 "UTC+3:30", // 19 853 "UTC+4:00", // 20 854 "UTC+4:30", // 21 855 "UTC+5:00", // 22 856 "UTC+5:30", // 23 857 "UTC+5:45", // 24 858 "UTC+6:00", // 25 859 "UTC+6:30", // 26 860 "UTC+7:00", // 27 861 "UTC+8:00", // 28 862 "UTC+8:30", // 29 863 "UTC+8:45", // 30 864 "UTC+9:00", // 31 865 "UTC+9:30", // 32 866 "UTC+10:00", // 33 867 "UTC+10:30", // 34 868 "UTC+11:00", // 35 869 "UTC+12:00", // 36 870 "UTC+12:45", // 37 871 "UTC+13:00", // 38 872 "UTC+14:00", // 39 873 ); 874 875 int sel = 0; 876 for(unsigned int i = 0; i < ARRAYLEN(menu_); ++i) 877 if(time_offs == get_time_seconds(menu_[i])) 878 { 879 sel = i; 880 break; 881 } 882 883 /* relies on menu internals */ 884 rb->do_menu(&menu, &sel, NULL, false); 885 886 /* see apps/menu.h */ 887 const char *label = menu_[sel]; 888 889 return get_time_seconds(label); 890 891#if 0 892 /* provided in case menu internals change */ 893 switch(rb->do_menu(&menu, &sel, NULL, false)) 894 { 895 case 0: case 1: case 2: 896 return (sel - 12) * 3600; 897 case 3: 898 return -9 * 3600 - 30 * 60; 899 case 4: case 5: case 6: case 7: case 8: case 9: 900 return (sel - 13) * 3600; 901 case 10: 902 return -3 * 3600 - 30 * 60; 903 case 11: 904 return -3 * 3600; 905 case 12: 906 return -3 * 3600 - 30 * 60; 907 case 13: case 14: case 15: case 16: case 17: case 18: 908 return (sel - 15) * 3600; 909 910 case 19: 911 return 3 * 3600 + 30 * 60; 912 case 20: 913 return 4 * 3600; 914 case 21: 915 return 4 * 3600 + 30 * 60; 916 case 22: 917 return 5 * 3600; 918 case 23: 919 return 5 * 3600 + 30 * 60; 920 case 24: 921 return 5 * 3600 + 45 * 60; 922 case 25: 923 return 6 * 3600; 924 case 26: 925 return 6 * 3600 + 30 * 60; 926 case 27: case 28: 927 return (sel - 20) * 3600; 928 case 29: 929 return 8 * 3600 + 30 * 60; 930 case 30: 931 return 8 * 3600 + 45 * 60; 932 case 31: 933 return 9 * 3600; 934 case 32: 935 return 9 * 3600 + 30 * 60; 936 case 33: 937 return 10 * 3600; 938 case 34: 939 return 10 * 3600 + 30 * 60; 940 case 35: case 36: 941 return (sel - 24) * 3600; 942 case 37: 943 return 12 * 3600 + 45 * 60; 944 case 38: case 39: 945 return (sel - 25) * 3600; 946 default: 947 rb->splash(0, "BUG: time zone fall-through: REPORT ME!!!"); 948 break; 949 } 950 return 0; 951#endif 952} 953#endif 954 955static void adv_menu(void) 956{ 957 MENUITEM_STRINGLIST(menu, "Advanced", NULL, 958 "Edit Account", 959 "Delete ALL accounts", 960#if CONFIG_RTC 961 "Change Time Offset", 962#endif 963 "Back"); 964 965 bool quit = false; 966 int sel = 0; 967 while(!quit) 968 { 969 switch(rb->do_menu(&menu, &sel, NULL, false)) 970 { 971 case 0: 972 edit_accts(); 973 break; 974 case 1: 975 if(danger_confirm()) 976 { 977 next_slot = 0; 978 save_accts(); 979 rb->splash(HZ, "It is done, my master."); 980 } 981 else 982 rb->splash(HZ, "Not confirmed."); 983 break; 984#if CONFIG_RTC 985 case 2: 986 time_offs = get_time_offs(); 987 break; 988 case 3: 989#else 990 case 2: 991#endif 992 quit = 1; 993 break; 994 default: 995 break; 996 } 997 } 998} 999 1000/* displays the help text */ 1001static void show_help(void) 1002{ 1003 1004#ifdef HAVE_LCD_COLOR 1005 rb->lcd_set_foreground(LCD_WHITE); 1006 rb->lcd_set_background(LCD_BLACK); 1007#endif 1008 1009 rb->lcd_setfont(FONT_UI); 1010 1011 static char *help_text[] = { "One-Time Password Manager", "", 1012 "Introduction", "", 1013 "This", "plugin", "allows", "you", "to", "generate", "one-time", "passwords", "to", "provide", "a", "second", "factor", "of", "authentication", "for", "services", "that", "support", "it.", 1014 "It", "suppports", "both", "event-based", "(HOTP),", "and", "time-based", "(TOTP)", "password", "schemes.", 1015 "In", "order", "to", "ensure", "proper", "functioning", "of", "time-based", "passwords", "ensure", "that", "the", "clock", "is", "accurate", "to", "within", "30", "seconds", "of", "actual", "time.", 1016 "Note", "that", "some", "devices", "lack", "a", "real-time", "clock,", "so", "time-based", "passwords", "are", "not", "supported", "on", "those", "targets." }; 1017 1018 struct style_text style[] = { 1019 {0, TEXT_CENTER | TEXT_UNDERLINE}, 1020 {2, C_RED}, 1021 LAST_STYLE_ITEM 1022 }; 1023 1024 display_text(ARRAYLEN(help_text), help_text, style, NULL, true); 1025} 1026 1027/* this is the plugin entry point */ 1028enum plugin_status plugin_start(const void* parameter) 1029{ 1030 (void)parameter; 1031 1032 /* self-test with RFC 4226 values */ 1033 if(HOTP("12345678901234567890", rb->strlen("12345678901234567890"), 1, 6) != 287082) 1034 { 1035 return PLUGIN_ERROR; 1036 } 1037 1038 size_t bufsz; 1039 accounts = rb->plugin_get_buffer(&bufsz); 1040 max_accts = bufsz / sizeof(struct account_t); 1041 1042 if(!read_accts()) 1043#if CONFIG_RTC 1044 { 1045 time_offs = get_time_offs(); 1046 } 1047#else 1048 { 1049 ; 1050 } 1051#endif 1052 1053 MENUITEM_STRINGLIST(menu, "One-Time Password Manager", NULL, 1054 "Add Account", 1055 "Generate Code", 1056 "Help", 1057 "Advanced", 1058 "Quit"); 1059 1060 bool quit = false; 1061 int sel = 0; 1062 while(!quit) 1063 { 1064 switch(rb->do_menu(&menu, &sel, NULL, false)) 1065 { 1066 case 0: 1067 add_acct(); 1068 break; 1069 case 1: 1070 gen_codes(); 1071 break; 1072 case 2: 1073 show_help(); 1074 break; 1075 case 3: 1076 adv_menu(); 1077 break; 1078 case 4: 1079 quit = 1; 1080 break; 1081 default: 1082 break; 1083 } 1084 } 1085 1086 /* save to disk */ 1087 save_accts(); 1088 1089 /* tell Rockbox that we have completed successfully */ 1090 return PLUGIN_OK; 1091}