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) 2008 Nils Wallménius
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#include "plugin.h"
22#include "lib/playback_control.h"
23#include "lib/md5.h"
24
25
26#define KEYBOX_FILE PLUGIN_APPS_DATA_DIR "/keybox.dat"
27#define BLOCK_SIZE 8
28#define MAX_ENTRIES 12*BLOCK_SIZE /* keep this a multiple of BLOCK_SIZE */
29#define FIELD_LEN 32 /* should be enough for anyone ;) */
30
31/* The header begins with the unencrypted salt (4 bytes) padded with 4 bytes of
32 zeroes. After that comes the encrypted hash of the master password (16 bytes) */
33
34#define HEADER_LEN 24
35
36enum
37{
38 FILE_OPEN_ERROR = -1
39};
40
41struct pw_entry
42{
43 bool used;
44 char title[FIELD_LEN];
45 char name[FIELD_LEN];
46 char password[FIELD_LEN];
47 struct pw_entry *next;
48};
49
50struct pw_list
51{
52 struct pw_entry first; /* always points to the first element in the list */
53 struct pw_entry entries[MAX_ENTRIES];
54 int num_entries;
55} pw_list;
56
57/* use this to access hashes in different ways, not byte order
58 independent but does it matter? */
59union hash
60{
61 uint8_t bytes[16];
62 uint32_t words[4];
63};
64
65static char buffer[sizeof(struct pw_entry)*MAX_ENTRIES];
66static int bytes_read = 0; /* bytes read into the buffer */
67static struct gui_synclist kb_list;
68static union hash key;
69static char master_pw[FIELD_LEN];
70static uint32_t salt;
71static union hash pwhash;
72static bool data_changed = false;
73
74static void encrypt_buffer(char *buf, size_t size, uint32_t *key);
75static void decrypt_buffer(char *buf, size_t size, uint32_t *key);
76
77/* the following two functions are the reference TEA implementation by
78 David Wheeler and Roger Needham taken from
79 http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm */
80
81static void do_encrypt(uint32_t* v, uint32_t* k)
82{
83 uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
84 static const uint32_t delta=0x9e3779b9; /* a key schedule constant */
85 uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
86 for (i=0; i < 32; i++) { /* basic cycle start */
87 sum += delta;
88 v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
89 v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); /* end cycle */
90 }
91 v[0]=v0; v[1]=v1;
92}
93
94static void do_decrypt(uint32_t* v, uint32_t* k)
95{
96 uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
97 static const uint32_t delta=0x9e3779b9; /* a key schedule constant */
98 uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
99 for (i=0; i<32; i++) { /* basic cycle start */
100 v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
101 v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
102 sum -= delta; /* end cycle */
103 }
104 v[0]=v0; v[1]=v1;
105}
106
107static int context_item_cb(int action,
108 const struct menu_item_ex *this_item,
109 struct gui_synclist *this_list)
110{
111 int i = (intptr_t)this_item;
112 (void)this_list;
113
114 if (action == ACTION_REQUEST_MENUITEM
115 && pw_list.num_entries == 0
116 && (i != 0 && i != 5))
117 {
118 return ACTION_EXIT_MENUITEM;
119 }
120 return action;
121}
122
123MENUITEM_STRINGLIST(context_m, "Context menu", context_item_cb,
124 "Add entry",
125 "Edit title", "Edit user name", "Edit password",
126 "Delete entry",
127 "Playback Control");
128
129static const char* kb_list_cb(int selected_item, void *data,
130 char *buffer, size_t buffer_len)
131{
132 (void)data;
133 int i;
134 struct pw_entry *entry = pw_list.first.next;
135 for (i = 0; i < selected_item; i++)
136 {
137 if (entry)
138 entry = entry->next;
139 }
140 if (!entry)
141 return NULL;
142
143 rb->snprintf(buffer, buffer_len, "%s", entry->title);
144
145 return buffer;
146}
147
148static void init_ll(void)
149{
150 pw_list.first.next = &pw_list.entries[0];
151 pw_list.entries[0].next = NULL;
152 pw_list.num_entries = 0;
153}
154
155static void delete_entry(int selected_item)
156{
157 int i;
158 struct pw_entry *entry = &pw_list.first;
159 struct pw_entry *entry2;
160
161 /* find the entry before the one to delete */
162 for (i = 0; i < selected_item; i++)
163 {
164 if (entry->next)
165 entry = entry->next;
166 }
167 entry2 = entry->next;
168 if (!entry2)
169 return;
170
171 entry->next = entry2->next;
172
173 entry2->used = false;
174 entry2->name[0] = '\0';
175 entry2->password[0] = '\0';
176 entry2->next = NULL;
177
178 rb->gui_synclist_set_nb_items(&kb_list, --pw_list.num_entries);
179 if(!pw_list.num_entries)
180 init_ll();
181 data_changed = true;
182}
183
184static void add_entry(int selected_item)
185{
186 int i, j;
187 struct pw_entry *entry = pw_list.first.next;
188 for (i = 0; i < MAX_ENTRIES && pw_list.entries[i].used; i++)
189 ;
190
191 if (MAX_ENTRIES == i)
192 {
193 rb->splash(HZ, "Password list full");
194 return;
195 }
196
197 rb->splash(HZ, "Enter title");
198 pw_list.entries[i].title[0] = '\0';
199 if (rb->kbd_input(pw_list.entries[i].title, FIELD_LEN, NULL) < 0)
200 return;
201
202 rb->splash(HZ, "Enter name");
203 pw_list.entries[i].name[0] = '\0';
204 if (rb->kbd_input(pw_list.entries[i].name, FIELD_LEN, NULL) < 0)
205 {
206 pw_list.entries[i].title[0] = '\0';
207 return;
208 }
209
210 rb->splash(HZ, "Enter password");
211 pw_list.entries[i].password[0] = '\0';
212 if (rb->kbd_input(pw_list.entries[i].password, FIELD_LEN, NULL) < 0)
213 {
214 pw_list.entries[i].title[0] = '\0';
215 pw_list.entries[i].name[0] = '\0';
216 return;
217 }
218
219 for (j = 0; j < selected_item; j++)
220 {
221 if (entry->next)
222 entry = entry->next;
223 }
224
225 rb->gui_synclist_set_nb_items(&kb_list, ++pw_list.num_entries);
226
227 pw_list.entries[i].used = true;
228 pw_list.entries[i].next = entry->next;
229
230 entry->next = &pw_list.entries[i];
231
232 if (entry->next == entry)
233 entry->next = NULL;
234
235 data_changed = true;
236}
237
238static void edit_title(int selected_item)
239{
240 int i;
241 struct pw_entry *entry = pw_list.first.next;
242 for (i = 0; i < selected_item; i++)
243 {
244 if (entry->next)
245 entry = entry->next;
246 }
247 if (rb->kbd_input(entry->title, FIELD_LEN, NULL) == 0)
248 data_changed = true;
249}
250
251static void edit_name(int selected_item)
252{
253 int i;
254 struct pw_entry *entry = pw_list.first.next;
255 for (i = 0; i < selected_item; i++)
256 {
257 if (entry->next)
258 entry = entry->next;
259 }
260 if (rb->kbd_input(entry->name, FIELD_LEN, NULL) == 0)
261 data_changed = true;
262}
263
264static void edit_pw(int selected_item)
265{
266 int i;
267 struct pw_entry *entry = pw_list.first.next;
268 for (i = 0; i < selected_item; i++)
269 {
270 if (entry->next)
271 entry = entry->next;
272 }
273 if (rb->kbd_input(entry->password, FIELD_LEN, NULL) == 0)
274 data_changed = true;
275}
276
277static void context_menu(int selected_item)
278{
279 int selection = 0, result;
280 bool exit = false;
281
282 do {
283 result = rb->do_menu(&context_m, &selection, NULL, false);
284 switch (result) {
285 case 0:
286 add_entry(selected_item);
287 return;
288 case 1:
289 edit_title(selected_item);
290 return;
291 case 2:
292 edit_name(selected_item);
293 return;
294 case 3:
295 edit_pw(selected_item);
296 return;
297 case 4:
298 delete_entry(selected_item);
299 return;
300 case 5:
301 playback_control(NULL);
302 return;
303 default:
304 exit = true;
305 break;
306 }
307 rb->yield();
308 } while (!exit);
309}
310
311static void splash_pw(int selected_item)
312{
313 int i;
314 struct pw_entry *entry = pw_list.first.next;
315
316 for (i = 0; i < selected_item; i++)
317 {
318 if (entry->next)
319 entry = entry->next;
320 }
321 if (entry->name[0] != '\0')
322 rb->splashf(0, "%s %s", entry->name, entry->password);
323 else
324 rb->splashf(0, "%s", entry->password);
325 rb->get_action(CONTEXT_STD, TIMEOUT_BLOCK);
326}
327
328static void hash_pw(union hash *out)
329{
330 int i;
331 struct md5_s pw_md5;
332
333 InitMD5(&pw_md5);
334 AddMD5(&pw_md5, master_pw, rb->strlen(master_pw));
335 EndMD5(&pw_md5);
336
337 for (i = 0; i < 4; i++)
338 out->words[i] = htole32(pw_md5.p_digest[i]);
339}
340
341static void make_key(void)
342{
343 int i;
344 char buf[sizeof(master_pw) + sizeof(salt) + 1] = {0};
345 struct md5_s key_md5;
346 size_t len = rb->strlen(master_pw);
347
348 rb->strlcpy(buf, master_pw, sizeof(buf));
349
350 rb->memcpy(&buf[len], &salt, sizeof(salt));
351
352 InitMD5(&key_md5);
353 AddMD5(&key_md5, buf, rb->strlen(buf));
354 EndMD5(&key_md5);
355
356 for (i = 0; i < 4; i++)
357 key.words[i] = key_md5.p_digest[i];
358}
359
360static void decrypt_buffer(char *buf, size_t size, uint32_t *key)
361{
362 unsigned int i;
363 uint32_t block[2];
364
365 for (i = 0; i < size/BLOCK_SIZE; i++)
366 {
367 rb->memcpy(&block[0], &buf[i*BLOCK_SIZE], sizeof(block));
368
369 block[0] = letoh32(block[0]);
370 block[1] = letoh32(block[1]);
371
372 do_decrypt(&block[0], key);
373
374 /* byte swap one block */
375 block[0] = letoh32(block[0]);
376 block[1] = letoh32(block[1]);
377
378 rb->memcpy(&buf[i*BLOCK_SIZE], &block[0], sizeof(block));
379 }
380}
381
382static void encrypt_buffer(char *buf, size_t size, uint32_t *key)
383{
384 unsigned int i;
385 uint32_t block[2];
386
387 for (i = 0; i < size/BLOCK_SIZE; i++)
388 {
389 rb->memcpy(&block[0], &buf[i*BLOCK_SIZE], sizeof(block));
390
391 /* byte swap one block */
392 block[0] = htole32(block[0]);
393 block[1] = htole32(block[1]);
394
395 do_encrypt(&block[0], key);
396
397 block[0] = htole32(block[0]);
398 block[1] = htole32(block[1]);
399
400 rb->memcpy(&buf[i*BLOCK_SIZE], &block[0], sizeof(block));
401 }
402}
403
404static int parse_buffer(void)
405{
406 int i;
407 int len;
408 struct pw_entry *entry = pw_list.first.next;
409 char *start, *end;
410 start = &buffer[HEADER_LEN];
411
412 rb->memcpy(&salt, &buffer[0], sizeof(salt));
413 make_key();
414
415 decrypt_buffer(&buffer[8], bytes_read - 8, &key.words[0]);
416
417 if (rb->memcmp(&buffer[8], &pwhash, sizeof(union hash)))
418 {
419 rb->splash(HZ*2, "Wrong password");
420 return -1;
421 }
422
423 for (i = 0; i < MAX_ENTRIES; i++)
424 {
425 end = rb->strchr(start, '\0'); /* find eol */
426 len = end - &buffer[HEADER_LEN];
427 if ((len > bytes_read + HEADER_LEN) || start == end)
428 {
429 break;
430 }
431
432 rb->strlcpy(entry->title, start, FIELD_LEN);
433 start = end + 1;
434
435 end = rb->strchr(start, '\0'); /* find eol */
436 len = end - &buffer[HEADER_LEN];
437 if (len > bytes_read + HEADER_LEN)
438 {
439 break;
440 }
441
442 rb->strlcpy(entry->name, start, FIELD_LEN);
443 start = end + 1;
444
445 end = rb->strchr(start, '\0'); /* find eol */
446 len = end - &buffer[HEADER_LEN];
447 if (len > bytes_read + HEADER_LEN)
448 {
449 break;
450 }
451 rb->strlcpy(entry->password, start, FIELD_LEN);
452 start = end + 1;
453 entry->used = true;
454 if (i + 1 < MAX_ENTRIES - 1)
455 {
456 entry->next = &pw_list.entries[i+1];
457 entry = entry->next;
458 }
459 else
460 {
461 break;
462 }
463 }
464 entry->next = NULL;
465 pw_list.num_entries = i;
466 rb->gui_synclist_set_nb_items(&kb_list, pw_list.num_entries);
467 return 0;
468}
469
470static void write_output(int fd)
471{
472 int i;
473 size_t len, size;
474 char *p = &buffer[HEADER_LEN]; /* reserve space for salt + hash */
475
476 rb->memcpy(&buffer[8], &pwhash, sizeof(union hash));
477 struct pw_entry *entry = pw_list.first.next;
478
479 for (i = 0; i < pw_list.num_entries; i++)
480 {
481 len = rb->strlen(entry->title);
482 rb->strlcpy(p, entry->title, len+1);
483 p += len+1;
484 len = rb->strlen(entry->name);
485 rb->strlcpy(p, entry->name, len+1);
486 p += len+1;
487 len = rb->strlen(entry->password);
488 rb->strlcpy(p, entry->password, len+1);
489 p += len+1;
490 if (entry->next)
491 entry = entry->next;
492 }
493 *p++ = '\0'; /* mark the end of the list */
494
495 /* round up to a number divisible by BLOCK_SIZE */
496 size = ((p - buffer + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE;
497
498 salt = rb->rand();
499 make_key();
500
501 encrypt_buffer(&buffer[8], size, &key.words[0]);
502 rb->memcpy(&buffer[0], &salt, sizeof(salt));
503
504 rb->write(fd, &buffer, size);
505}
506
507static int enter_pw(char *pw_buf, size_t buflen, bool new_pw)
508{
509 char buf[2][sizeof(master_pw)];
510 rb->memset(buf, 0, sizeof(buf));
511 rb->memset(master_pw, 0, sizeof(master_pw));
512
513 if (new_pw)
514 {
515 rb->splash(HZ, "Enter new master password");
516 if (rb->kbd_input(buf[0], sizeof(buf[0]), NULL) < 0)
517 return -1;
518
519 rb->splash(HZ, "Confirm master password");
520 if (rb->kbd_input(buf[1], sizeof(buf[1]), NULL) < 0)
521 return -1;
522
523 if (rb->strcmp(buf[0], buf[1]))
524 {
525 rb->splash(HZ, "Password mismatch");
526 return -1;
527 }
528 else
529 {
530 rb->strlcpy(pw_buf, buf[0], buflen);
531 hash_pw(&pwhash);
532 return 0;
533 }
534 }
535
536 rb->splash(HZ, "Enter master password");
537 if (rb->kbd_input(pw_buf, buflen, NULL) < 0)
538 return -1;
539 hash_pw(&pwhash);
540 return 0;
541}
542
543static int keybox(void)
544{
545 int button, fd;
546 bool new_file = !rb->file_exists(KEYBOX_FILE);
547 bool done = false;
548
549 if (enter_pw(master_pw, sizeof (master_pw), new_file))
550 return 0;
551
552 /* Read the existing file */
553 if (!new_file)
554 {
555 fd = rb->open(KEYBOX_FILE, O_RDONLY);
556 if (fd < 0)
557 return FILE_OPEN_ERROR;
558 bytes_read = rb->read(fd, &buffer, sizeof(buffer));
559
560 rb->close(fd);
561
562 if (parse_buffer())
563 return 0;
564 }
565
566 while (!done)
567 {
568 rb->gui_synclist_draw(&kb_list);
569 button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
570 if (rb->gui_synclist_do_button(&kb_list, &button))
571 continue;
572
573 switch (button)
574 {
575 case ACTION_STD_OK:
576 splash_pw(rb->gui_synclist_get_sel_pos(&kb_list));
577 break;
578 case ACTION_STD_CONTEXT:
579 context_menu(rb->gui_synclist_get_sel_pos(&kb_list));
580 break;
581 case ACTION_STD_CANCEL:
582 done = true;
583 break;
584 }
585 rb->yield();
586 }
587
588 if (data_changed)
589 {
590 fd = rb->open(KEYBOX_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0666);
591 if (fd < 0)
592 return FILE_OPEN_ERROR;
593 write_output(fd);
594 rb->close(fd);
595 }
596
597 return 0;
598}
599
600static void reset(void)
601{
602 static const char *message_lines[]=
603 {"Do you really want", "to reset keybox?"};
604 static const char *yes_lines[]=
605 {"Keybox reset."};
606 static const struct text_message message={message_lines, 2};
607 static const struct text_message yes_message={yes_lines, 1};
608
609 if(rb->gui_syncyesno_run(&message, &yes_message, NULL) == YESNO_YES)
610 {
611 rb->remove(KEYBOX_FILE);
612 rb->memset(&buffer, 0, sizeof(buffer));
613 rb->memset(&pw_list, 0, sizeof(pw_list));
614 rb->gui_synclist_set_nb_items(&kb_list, 0);
615 init_ll();
616 }
617}
618
619static int main_menu(void)
620{
621 int selection = 0, result, ret;
622 bool exit = false;
623
624 MENUITEM_STRINGLIST(menu, "Keybox", NULL,
625 "Enter Keybox", "Reset Keybox",
626 "Playback Control", "Exit");
627
628 do {
629 result = rb->do_menu(&menu, &selection, NULL, false);
630 switch (result) {
631 case 0:
632 ret = keybox();
633 if (ret)
634 return ret;
635 break;
636 case 1:
637 reset();
638 break;
639 case 2:
640 playback_control(NULL);
641 break;
642 case 3:
643 exit = true;
644 break;
645 }
646 rb->yield();
647 } while (!exit);
648
649 return 0;
650}
651
652enum plugin_status plugin_start(const void *parameter)
653{
654 (void)parameter;
655 int ret;
656
657 rb->gui_synclist_init(&kb_list, &kb_list_cb, NULL, false, 1, NULL);
658
659 rb->gui_synclist_set_title(&kb_list, "Keybox", NOICON);
660 rb->gui_synclist_set_nb_items(&kb_list, 0);
661 rb->gui_synclist_select_item(&kb_list, 0);
662
663 init_ll();
664 ret = main_menu();
665
666 switch (ret)
667 {
668 case FILE_OPEN_ERROR:
669 rb->splash(HZ*2, "Error opening file");
670 return PLUGIN_ERROR;
671 }
672
673 return PLUGIN_OK;
674}