Skip to content
Snippets Groups Projects
shell.c 14.27 KiB
#include "shell.h"
#include "../ds1307.h"
#include "../log.h"
#include "../door.h"
#include "../cardreader_interface.h"

#define SHELL_LOGIN_PROMPT "\nMezu Login: "

#define MIN(a,b) ((a) < (b) ? (a) : (b))

uint8_t shell_check_password(uint8_t user, char* password);
void shell_set_password(uint8_t user, char* password);
uint8_t shell_find_user(char* username);

void cmd_help(readline_parsed_cmd_t* cmd);
void cmd_passwd(readline_parsed_cmd_t* cmd);
void cmd_keyslot_init(readline_parsed_cmd_t* cmd);
void cmd_keyslot_clear(readline_parsed_cmd_t* cmd);
void cmd_keyslot_list(readline_parsed_cmd_t* cmd);
void cmd_keyslot_disable(readline_parsed_cmd_t* cmd);
void cmd_keyslot_enable(readline_parsed_cmd_t* cmd);
void cmd_keyslot_restore(readline_parsed_cmd_t* cmd);
void cmd_lock(readline_parsed_cmd_t* cmd);
void cmd_unlock(readline_parsed_cmd_t* cmd);
void cmd_logout(readline_parsed_cmd_t* cmd);
void cmd_status(readline_parsed_cmd_t* cmd);
void cmd_keyslot_alarm_stop(readline_parsed_cmd_t* cmd);
void cmd_reset(readline_parsed_cmd_t* cmd);
void cmd_date(readline_parsed_cmd_t* cmd);
void cmd_show_log(readline_parsed_cmd_t* cmd);
void cmd_card_init_key(readline_parsed_cmd_t* cmd);
void cmd_card_clear_key(readline_parsed_cmd_t* cmd);

readline_supported_cmd_t readline_commands[] = {{"help"        , 0, 0, cmd_help},
                                                {"logout"      , 0, 0, cmd_logout},
                                                {"exit"        , 0, 0, cmd_logout},
                                                {"passwd"      , 0, 1, cmd_passwd},
                                                {"unlock"      , 0, 0, cmd_unlock},
                                                {"lock"        , 0, 1, cmd_lock},
                                                {"status"      , 0, 0, cmd_status},
                                                {"reset"       , 0, 0, cmd_reset},
                                                {"initks"      , 0, 2, cmd_keyslot_init},
                                                {"clearks"     , 1, 1, cmd_keyslot_clear},
                                                {"disableks"   , 1, 1, cmd_keyslot_disable},
                                                {"enableks"    , 1, 1, cmd_keyslot_enable},
                                                {"restoreks"   , 1, 1, cmd_keyslot_restore},
                                                {"urusai"      , 0, 0, cmd_keyslot_alarm_stop},
                                                {"ls"          , 0, 0, cmd_keyslot_list},
                                                {"date"        , 0, 2, cmd_date},
                                                {"log"         , 0, 0, cmd_show_log},
                                                {"initcardkey" , 1, 1, cmd_card_init_key},
                                                {"clearcardkey", 0, 0, cmd_card_clear_key},
                                                {0,0,0,0}};

#define SHELL_STATE_READ_USER 0
#define SHELL_STATE_READ_PASSWORD 1
#define SHELL_STATE_LOGGED_IN 3
#define SHELL_STATE_INVALID_LOGIN 4
#define SHELL_STATE_PASSWD1 5
#define SHELL_STATE_PASSWD2 6

static uint8_t shell_state = SHELL_STATE_READ_USER;


static char* users[NUM_USERS] = {"root", "admin", 0 };

#define SHELL_USER_INVALID 0xFF
static uint8_t login_user = SHELL_USER_INVALID;

static uint8_t passwd_user = SHELL_USER_INVALID;

#define SHELL_MAX_PW_LEN 20
static char passwd_pw[SHELL_MAX_PW_LEN];

#define SHELL_LOGIN_DELAY 200
static uint8_t shell_login_delay = SHELL_LOGIN_DELAY;

void shell_init(void){
	readline_set_mode(SHELL_LOGIN_PROMPT, 1 ,0);
	readline_init();
}

void shell_tick(void){
	if (shell_login_delay > 0){
		shell_login_delay--;
		if (shell_login_delay == 0 && shell_state == SHELL_STATE_INVALID_LOGIN){
			printf_P(PSTR("Login incorrect\n"));
			login_user = SHELL_USER_INVALID;
			shell_state = SHELL_STATE_READ_USER;
			readline_set_mode(SHELL_LOGIN_PROMPT, 1 ,0);
			readline_write_prompt();
		}
	}
}

void shell_show_welcome_prompt(void){
	readline_show_welcome_prompt();
}

void shell_process(void){
	readline_process();
}

void readline_handle_buffer(char* input_buffer){
	if (shell_state == SHELL_STATE_READ_USER){
		if (strlen(input_buffer)){
			login_user = shell_find_user(input_buffer);
			shell_state = SHELL_STATE_READ_PASSWORD;
			readline_set_mode("Password: ", 0 ,0);
		}
	} else if (shell_state == SHELL_STATE_READ_PASSWORD){
		if ((login_user != SHELL_USER_INVALID) && (shell_check_password(login_user, input_buffer))){
			shell_state = SHELL_STATE_LOGGED_IN;
			printf_P(PSTR("Login successful\n"));
			ds1307_write_current_date();
			if (door_get_status() & DOOR_STATUS_ALARM) log_append(LOG_EVENT_ALARM_LOGIN, login_user);
			printf_P(PSTR("\n"));
			readline_set_mode("> ", 1 ,1); //start parsing commands
		} else {
			login_user = SHELL_USER_INVALID;
			shell_state = SHELL_STATE_INVALID_LOGIN;
			shell_login_delay = SHELL_LOGIN_DELAY;
			printf_P(PSTR("\n"));
			readline_set_mode(0, 0 ,0);
		}
	} else if (shell_state == SHELL_STATE_INVALID_LOGIN){
		//ignore
	} else if (shell_state == SHELL_STATE_PASSWD1){
		memcpy(passwd_pw, input_buffer, MIN(strlen(input_buffer), SHELL_MAX_PW_LEN));
		readline_set_mode("Confirm Password: ", 0 ,0);
		shell_state = SHELL_STATE_PASSWD2;
	} else if (shell_state == SHELL_STATE_PASSWD2){
		if (strcmp(passwd_pw, input_buffer) == 0){
			shell_set_password(passwd_user, passwd_pw);
			printf_P(PSTR("Password changed\n"));
		} else {
			printf_P(PSTR("Passwords don't match\n"));
		}
		memset(passwd_pw, 0, SHELL_MAX_PW_LEN);
		
		shell_state = SHELL_STATE_LOGGED_IN;
		readline_set_mode("> ", 1 ,1);
	}
}

void cmd_help(readline_parsed_cmd_t* cmd){
	printf_P(PSTR("Known Commands:\n\
lock [-f]     - Lock Door\n\
unlock        - Unlock door\n\
status        - Display system status\n\
reset         - Reset controller\n\
passwd [user] - Change user password\n\
initks    x   - Initialize keyslot x with a new random key\n\
clearks   x   - Clear keyslot x (delete key)\n\
disableks x   - Disable keyslot x (but keep key)\n\
enableks  x   - Enable a disabled keyslot\n\
restoreks x   - Restore keyslot content from last valid key\n\
ls            - List keyslots (only key stubs)\n\
initcardkey x - Program the key of keyslot x on the card in the cardreader\n\
clearcardkey  - Clear the key of the card currently in the cardreader\n\
date [dd-mm-yy] hh:mm:ss - Set system date and time\n\
log           - Show systemlog\n\
urusai        - Turn Alarm off\n\
logout        - Exit shell\n\
quit          - Exit shell\n"));
}

extern void MGMT_transmit_buffer_hex(unsigned char* data, unsigned char length);

void cmd_keyslot_init(readline_parsed_cmd_t* cmd){
	uint8_t force = 0;
	uint8_t usage_error = 0;
	char* numarg = 0;
	uint16_t keyslot_index = 0xFFFF;
	if (cmd->num_args == 0){
		keyslot_index = keystore_find_empty_slot();
	} else if (cmd->num_args == 1){
		if (strcmp(cmd->args[0], "-f") == 0){
			force = 1;
			keyslot_index = keystore_find_empty_slot();
		} else {
			numarg = cmd->args[0];
		}
	} else if (cmd->num_args == 2){
		if (strcmp(cmd->args[0], "-f") == 0){
			force = 1;
			numarg = cmd->args[1];
		} else {
			usage_error = 1;
		}
	} else {
		usage_error = 1;
	}
	
	if (numarg) {
		char* endptr;
		keyslot_index = strtol(numarg, &endptr, 10);
		printf_P(PSTR("Keyslot '%s' %i\n"), numarg, keyslot_index);
		if (*endptr != 0) usage_error = 1; //number parsing didn't reach the end of the string
	}
	
	if (usage_error){
		printf_P(PSTR("Usage: initks [-f] index\n"));
		return;
	}
	if (keyslot_index > KEY_COUNT){
		printf_P(PSTR("Slot number too big (%i)\n"), keyslot_index);
		return;
	}
	
	if (!force){
		if (keystore_read_slot_status(keyslot_index) != KEYSLOT_EMPTY){
			printf_P(PSTR("Slot %i not empty\n"), keyslot_index);
			return;
		}
	}
	
	KEY new_key;
	if (keystore_init_slot(keyslot_index, new_key)){
		printf_P(PSTR("Slot %i initialized\nNew key: "), keyslot_index);
		MGMT_transmit_buffer_hex(new_key, sizeof(KEY));
		printf_P(PSTR("\n"));
	} else {
		printf_P(PSTR("Failed!\n"));
	}
}

void cmd_keyslot_disable(readline_parsed_cmd_t* cmd){
	uint16_t keyslot_index;
	char* endptr;
	keyslot_index = strtol(cmd->args[0], &endptr, 10);
// 	printf_P(PSTR("Keyslot '%s' %i\n"), cmd->args[0], keyslot_index);
	if (*endptr != 0){//number parsing didn't reach the end of the string
		printf_P(PSTR("Usage: disableks index\n"));
	}
	
	if (keystore_disable_slot(keyslot_index)){
		printf_P(PSTR("OK\n"));
	} else {
		printf_P(PSTR("Failed\n"));
	}
}
void cmd_keyslot_enable(readline_parsed_cmd_t* cmd){
	uint16_t keyslot_index;
	char* endptr;
	keyslot_index = strtol(cmd->args[0], &endptr, 10);
// 	printf_P(PSTR("Keyslot '%s' %i\n"), cmd->args[0], keyslot_index);
	if (*endptr != 0){//number parsing didn't reach the end of the string
		printf_P(PSTR("Usage: enableks index\n"));
	}
	
	if (keystore_enable_slot(keyslot_index)){
		printf_P(PSTR("OK\n"));
	} else {
		printf_P(PSTR("Failed\n"));
	}
}

void cmd_keyslot_clear(readline_parsed_cmd_t* cmd){
	uint16_t keyslot_index;
	char* endptr;
	keyslot_index = strtol(cmd->args[0], &endptr, 10);
// 	printf_P(PSTR("Keyslot '%s' %i\n"), cmd->args[0], keyslot_index);
	if (*endptr != 0){//number parsing didn't reach the end of the string
		printf_P(PSTR("Usage: clearks index\n"));
	}
	
	if (keystore_clear_slot(keyslot_index)){
		printf_P(PSTR("OK\n"));
	} else {
		printf_P(PSTR("Failed\n"));
	}
}

void cmd_keyslot_restore(readline_parsed_cmd_t* cmd){
	uint16_t keyslot_index;
	char* endptr;
	keyslot_index = strtol(cmd->args[0], &endptr, 10);
// 	printf_P(PSTR("Keyslot '%s' %i\n"), cmd->args[0], keyslot_index);
	if (*endptr != 0){//number parsing didn't reach the end of the string
		printf_P(PSTR("Usage: restoreks index\n"));
	}
	
	if (keystore_restore_slot(keyslot_index)){
		printf_P(PSTR("OK\n"));
	} else {
		printf_P(PSTR("Failed\n"));
	}
	
}

void cmd_keyslot_list(readline_parsed_cmd_t* cmd){
	keystore_list();
}

void print_P(const char* pstr){
	printf_P(pstr);
}


void cmd_lock(readline_parsed_cmd_t* cmd){
	if ((cmd->num_args > 0) && (strcmp(cmd->args[0], "-f") == 0)){
		lock();
	} else {
		lock_checked(print_P);
	}
}
void cmd_unlock(readline_parsed_cmd_t* cmd){
	if ((cmd->num_args > 0) && (strcmp(cmd->args[0], "-f") == 0)){
		unlock();
	} else {
		unlock_checked(print_P);
	}
}

void cmd_status(readline_parsed_cmd_t* cmd){
	door_write_status();
	printf_P(PSTR("\n"));
	power_write_status();
	printf_P(PSTR("\n"));
}
void cmd_reset(readline_parsed_cmd_t* cmd){
	printf_P(PSTR("Restarting...\n"));
	wdt_enable(WDTO_15MS);
	while(1) {;}
}

void cmd_logout(readline_parsed_cmd_t* cmd){
	shell_state = SHELL_STATE_READ_USER;
	login_user = SHELL_USER_INVALID;
	readline_set_mode(SHELL_LOGIN_PROMPT, 1 ,0);
}

void cmd_passwd(readline_parsed_cmd_t* cmd){
	uint8_t user;
	if (cmd->num_args){
		user = shell_find_user(cmd->args[0]);
	} else {
		user = login_user;
	}
	if (user == SHELL_USER_INVALID){
		printf_P(PSTR("Unknown user\n"));
		return;
	}
	if (user != login_user && login_user != 0){
		printf_P(PSTR("Must be root\n"));
		return;
	}
	
	passwd_user = user;
	shell_state = SHELL_STATE_PASSWD1;
	readline_set_mode("New Password: ", 0 ,0);
}

void cmd_keyslot_alarm_stop(readline_parsed_cmd_t* cmd){
	if (!door_clear_alarm()){
		printf_P(PSTR("Error: Alarm condition still present\n"));
	}
}

void cmd_date(readline_parsed_cmd_t* cmd){
	if (cmd->num_args == 0){
		printf_P(PSTR("Current Date: "));
	} else if ((cmd->num_args == 1) || (cmd->num_args == 2)) {
		date_t new_date;
		uint8_t date_valid = 0;
// 		uint8_t time_valid = 0;
		uint16_t h,m,s,d,y;
		
		if (cmd->num_args == 2){
			if (sscanf_P(cmd->args[0], PSTR("%x-%x-%x"), &d, &m, &y) == 3){
				date_valid = 1;
				//printf_P(PSTR("%02x-%02x-%02x "), d, m, y);
				new_date.day = d;
				new_date.month = m;
				new_date.year = y;
			} else {
				printf_P(PSTR("Date '%s' invalid, use format: dd-mm-yy\n"), cmd->args[1]);
				return;
			}
		}
		
		if (sscanf_P(cmd->args[cmd->num_args - 1], PSTR("%x:%x:%x"), &h, &m, &s) == 3){
			//printf_P(PSTR("%02x:%02x:%02x\n"), h, m, s);
			new_date.hour = h;
			new_date.minute = m;
			new_date.second = s;
		} else {
			printf_P(PSTR("Time '%s' invalid, use format: hh-mm-ss\n"), cmd->args[0]);
		}
		
		if (date_valid){
			ds1307_set_date(&new_date);
		} else {
			ds1307_set_time((time_t*)&new_date);
		}
		printf_P(PSTR("New Date: "));
	}
	
	ds1307_write_current_date();
	printf_P(PSTR("\n"));
}

void cmd_card_init_key(readline_parsed_cmd_t* cmd){
	uint16_t keyslot_index;
	char* endptr;
	keyslot_index = strtol(cmd->args[0], &endptr, 10);
	if (*endptr != 0){//number parsing didn't reach the end of the string
		printf_P(PSTR("Usage: initcardkey index\n"));
	}
	
	uint8_t slot_status = keystore_read_slot_status(keyslot_index);
	if (slot_status == KEYSLOT_USED || slot_status == KEYSLOT_DISABLED){
		printf_P(PSTR("Programming Keyslot %i -> Card:"),keyslot_index);
		
		KEY key;
		if (keystore_read_key(keyslot_index, key)){
			cardreader_init_card_key(key);
		} else {
			printf_P(PSTR("Key read error\n"));
		}
	} else {
		printf_P(PSTR("Invalid keyslot %i\n"), keyslot_index);
	}
}
void cmd_card_clear_key(readline_parsed_cmd_t* cmd){
	printf_P(PSTR("Clearing card key:"));
	cardreader_clear_card_key();
}


uint8_t shell_find_user(char* username){
	uint8_t i = 0;
	while (users[i]){
		if (strcmp(username, users[i]) == 0){
			return i;
		}
		i++;
	}
	return SHELL_USER_INVALID;
}

uint8_t shell_check_password(uint8_t user, char* password){
	HASH ref_hash;
	HASH input_hash;
	uint8_t pw_len = MIN(strlen(password), SHELL_MAX_PW_LEN);
	uint16_t eeprom_addr = 32*user;
	uint32_t salt;
	eeprom_read_block(ref_hash, ((char*)eeprom_addr), sizeof(HASH));
	eeprom_read_block(&salt, ((char*)eeprom_addr) + sizeof(HASH), 4);
	
	uint8_t empty = 1;
	for (uint8_t i = 0; i < sizeof(HASH); i++){
		empty &= (ref_hash[i] == 0xFF);
	}
	if (empty) return user == 0; //if no password is set, allow root (for initial login, but not others)
	
	memcpy(password + pw_len, &salt, 4);
	sha1(&input_hash, password, (pw_len + 4) * 8);
	return memcmp(ref_hash, input_hash, sizeof(HASH)) == 0;
}

void shell_set_password(uint8_t user, char* password){
	HASH hash;
	uint16_t eeprom_addr = 32*user;
	uint8_t pw_len = MIN(strlen(password), SHELL_MAX_PW_LEN);
	uint32_t salt;
	rnd_get_bytes((uint8_t*)&salt, 4);
	memcpy(password + pw_len, &salt, 4);
	sha1(&hash, password, (pw_len+4) * 8);
	eeprom_write_block(hash, ((char*)eeprom_addr), sizeof(HASH));
	eeprom_write_block(&salt, ((char*)eeprom_addr) + sizeof(HASH), 4);
}

void cmd_show_log(readline_parsed_cmd_t* cmd){
	log_dump();
}