A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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}