A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 674 lines 17 kB view raw
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}