shellmatta_auth.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. /*
  2. * Copyright (c) 2022 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  3. *
  4. * This Source Code Form is subject to the terms of the Mozilla Public
  5. * License, v. 2.0. If a copy of the MPL was not distributed with this
  6. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  7. */
  8. /**
  9. * @file shellmatta_auth.c
  10. * @brief simple authentication method
  11. * @author Stefan Strobel <stefan.strobel@shimatta.net>
  12. */
  13. /**
  14. * @addtogroup shellmatta_auth
  15. * @{
  16. */
  17. #include "shellmatta.h"
  18. #include "shellmatta_auth.h"
  19. #include "shellmatta_history.h"
  20. #include "shellmatta_utils.h"
  21. #include "shellmatta_escape.h"
  22. #include <stdint.h>
  23. #include <stddef.h>
  24. #include <string.h>
  25. /**
  26. * @brief wraps the input to the login command and mimics the editability of the standard shell input
  27. * @param[in, out] inst shellmatta instance to work with
  28. * @param[in] data one byte of data the user put in
  29. * @param[in] hide true: do not print anything tho the output (e.g. password input)
  30. * @return #SHELLMATTA_BUSY => reading in data
  31. * #SHELLMATTA_OK => delimiter (e.g. enter) detected
  32. */
  33. shellmatta_retCode_t inputWrapper(shellmatta_instance_t *inst, char data, bool hide)
  34. {
  35. shellmatta_retCode_t ret = SHELLMATTA_BUSY;
  36. bool echoEnabledBackup = inst->echoEnabled;
  37. /** -# disable echo when hidden */
  38. inst->echoEnabled &= !hide;
  39. /** -# handle escape sequences */
  40. if(inst->escapeCounter != 0u)
  41. {
  42. escape_handleSequence(inst, data);
  43. }
  44. else if (inst->delimiter == data)
  45. {
  46. ret = SHELLMATTA_OK;
  47. }
  48. /** -# ignore newline as first character (to be compatible to
  49. * terminals sending newline after return */
  50. else if((0u == inst->inputCount) && ('\n' == data))
  51. {
  52. /* do nothing */
  53. }
  54. /** -# check for backspace */
  55. else if( ('\b' == data)
  56. || ('\x7f' == data))
  57. {
  58. utils_removeChars(inst, 1u, true);
  59. }
  60. /** -# check for start of escape sequence */
  61. else if('\x1b' == data)
  62. {
  63. inst->escapeCounter = 1u;
  64. }
  65. else
  66. {
  67. utils_insertChars(inst, &data, 1u);
  68. }
  69. inst->echoEnabled = echoEnabledBackup;
  70. return ret;
  71. }
  72. /**
  73. * @brief searches the user list for a passed username and returns the userId
  74. * @param[in] inst shellmatta instance to work with
  75. * @param[in] username username to search for - pointer to string
  76. * @return userId or 0 if user was not found
  77. */
  78. static uint32_t getUserIdFromName(shellmatta_instance_t *inst, const char *username)
  79. {
  80. uint32_t userId = 0u;
  81. uint32_t i;
  82. for (i = 0u; i < inst->userListLength; i++)
  83. {
  84. /** -# search for user */
  85. if (0 == strcmp(inst->userList[i].username, username))
  86. {
  87. userId = inst->userList[i].userId;
  88. break;
  89. }
  90. }
  91. return userId;
  92. }
  93. /**
  94. * @brief checks the passed password and logs the user into the instance on success
  95. * @param[in, out] handle shellmatta handle to work with
  96. * @param[in] userId userId to check the password for
  97. * @param[in] password password to check for - pointer to string
  98. */
  99. void checkPassword(shellmatta_handle_t handle, uint32_t userId, char *password)
  100. {
  101. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  102. uint32_t approvedUserId = 0u;
  103. uint32_t i;
  104. if (NULL != inst->checkFct)
  105. {
  106. approvedUserId = inst->checkFct(userId, password) == SHELLMATTA_OK ? userId : 0u;
  107. }
  108. else
  109. {
  110. for (i = 0u; i < inst->userListLength; i++)
  111. {
  112. /** -# search for user */
  113. if (inst->userList[i].userId == userId)
  114. {
  115. /** -# check password */
  116. if (0 == strcmp(inst->userList[i].password, password))
  117. {
  118. approvedUserId = inst->userList[i].userId;
  119. }
  120. break;
  121. }
  122. }
  123. }
  124. /** -# call log function - only for unsuccessful logins - successful logins are handled by #shellmatta_auth_login */
  125. if ((inst->logFct) && (0u == approvedUserId))
  126. {
  127. inst->logFct(userId, SHELLMATTA_AUTH_EVENT_LOGIN_FAILED);
  128. }
  129. /** -# print login result */
  130. if (0 == approvedUserId)
  131. {
  132. shellmatta_write(handle, "username or password is wrong\r\n", 31);
  133. }
  134. else
  135. {
  136. shellmatta_write(handle, "login successful\r\n", 18);
  137. (void)shellmatta_auth_login(handle, approvedUserId);
  138. }
  139. }
  140. /**
  141. * @brief handles the login based on username and password
  142. * @param[in] handle handle shellmatta instance handle
  143. * @param[in] arguments arguments containing a command name or alias
  144. * @param[in] length length of the arguments
  145. * @return #SHELLMATTA_OK
  146. * #SHELLMATTA_CONTINUE (waiting for input)
  147. */
  148. static shellmatta_retCode_t loginCmdFct(const shellmatta_handle_t handle, const char *arguments, uint32_t length)
  149. {
  150. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  151. shellmatta_retCode_t ret = SHELLMATTA_OK;
  152. char option;
  153. char *argument;
  154. uint32_t argLen;
  155. char *username = NULL;
  156. char *password = NULL;
  157. static const shellmatta_opt_long_t options[] =
  158. {
  159. {"username" , 'u', SHELLMATTA_OPT_ARG_REQUIRED},
  160. {"password" , 'p', SHELLMATTA_OPT_ARG_REQUIRED},
  161. {NULL , '\0', SHELLMATTA_OPT_ARG_NONE}
  162. };
  163. /** -# handle the login state machine */
  164. switch(inst->loginState)
  165. {
  166. /** -# init the login procedure - use directly passed credentials if any */
  167. case SHELLMATTA_AUTH_IDLE:
  168. ret = shellmatta_opt_long(handle, options, &option, &argument, &argLen);
  169. while(SHELLMATTA_OK == ret)
  170. {
  171. switch(option)
  172. {
  173. case 'u':
  174. if(NULL != argument)
  175. {
  176. username = argument;
  177. }
  178. break;
  179. case 'p':
  180. if(NULL != argument)
  181. {
  182. password = argument;
  183. }
  184. break;
  185. default:
  186. shellmatta_write(handle, "Unknown option\r\n", 16);
  187. return SHELLMATTA_USE_FAULT;
  188. break;
  189. }
  190. ret = shellmatta_opt_long(handle, options, &option, &argument, &argLen);
  191. }
  192. if ((NULL != username) && (NULL != password))
  193. {
  194. inst->tmpUserId = getUserIdFromName(inst, username);
  195. checkPassword(handle, inst->tmpUserId, password);
  196. }
  197. /** -# user passed username and start interactive password input */
  198. else if(NULL != username)
  199. {
  200. inst->inputCount = 0u;
  201. inst->cursor = 0u;
  202. inst->tmpUserId = getUserIdFromName(handle, username);
  203. shellmatta_write(handle, "\r\nenter password:\r\n", 19);
  204. inst->loginState = SHELLMATTA_AUTH_PASSWORD;
  205. ret = SHELLMATTA_CONTINUE;
  206. }
  207. else if (NULL != password)
  208. {
  209. shellmatta_write(handle, "Missing username\r\n", 18);
  210. ret = SHELLMATTA_USE_FAULT;
  211. }
  212. else
  213. {
  214. /** -# no credentials are passed with the command - start the interactive input */
  215. inst->inputCount = 0u;
  216. inst->cursor = 0u;
  217. shellmatta_write(handle, "enter username:\r\n", 17);
  218. inst->loginState = SHELLMATTA_AUTH_USERNAME;
  219. ret = SHELLMATTA_CONTINUE;
  220. }
  221. break;
  222. case SHELLMATTA_AUTH_USERNAME:
  223. /** -# read in password characters to the instance buffer */
  224. (void)shellmatta_read(handle, &argument, &argLen);
  225. if ((1 == argLen) && (SHELLMATTA_OK == inputWrapper(inst, argument[0], false)))
  226. {
  227. /** -# store user id */
  228. inst->buffer[inst->inputCount] = '\0';
  229. inst->tmpUserId = getUserIdFromName(handle, inst->buffer);
  230. /** -# reinitialize input */
  231. inst->inputCount = 0u;
  232. inst->cursor = 0u;
  233. shellmatta_write(handle, "\r\nenter password:\r\n", 19);
  234. inst->loginState = SHELLMATTA_AUTH_PASSWORD;
  235. }
  236. ret = SHELLMATTA_CONTINUE;
  237. break;
  238. case SHELLMATTA_AUTH_PASSWORD:
  239. /** -# read in password characters to the instance buffer - hiding output */
  240. (void)shellmatta_read(handle, &argument, &argLen);
  241. if ((1 == argLen) && (SHELLMATTA_OK == inputWrapper(inst, argument[0], true)))
  242. {
  243. /** -# check input username and password */
  244. inst->buffer[inst->inputCount] = '\0';
  245. shellmatta_write(handle, "\r\n", 2);
  246. checkPassword(handle, inst->tmpUserId, inst->buffer);
  247. inst->loginState = SHELLMATTA_AUTH_IDLE;
  248. }
  249. else
  250. {
  251. ret = SHELLMATTA_CONTINUE;
  252. }
  253. break;
  254. default:
  255. break;
  256. }
  257. (void)arguments;
  258. (void)length;
  259. return ret;
  260. }
  261. const shellmatta_cmd_t shellmatta_auth_loginCmd = {"login"
  262. , "li"
  263. , "Login command"
  264. , "login interactively or use -u <username> -p <password>"
  265. , loginCmdFct
  266. , NULL
  267. , NULL};
  268. /**
  269. * @brief handles the logout of the current session
  270. * @param[in] handle handle shellmatta instance handle
  271. * @param[in] arguments arguments containing a command name or alias
  272. * @param[in] length length of the arguments
  273. * @return #SHELLMATTA_OK
  274. */
  275. static shellmatta_retCode_t logoutCmdFct(const shellmatta_handle_t handle, const char *arguments, uint32_t length)
  276. {
  277. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  278. /** -# log the logout event */
  279. if (inst->logFct)
  280. {
  281. inst->logFct(inst->userId, SHELLMATTA_AUTH_EVENT_LOGOUT);
  282. }
  283. shellmatta_auth_logout(handle);
  284. shellmatta_write(handle, "good bye\r\n", 10);
  285. (void)arguments;
  286. (void)length;
  287. return SHELLMATTA_OK;
  288. }
  289. const shellmatta_cmd_t shellmatta_auth_logoutCmd = {"logout"
  290. , "lo"
  291. , "Logout command"
  292. , "logs out the currently logged in user"
  293. , logoutCmdFct
  294. , NULL
  295. , NULL};
  296. /**
  297. * @brief initializes the shellmatta authentication system
  298. * @param[in] handle shellmatta instance handle
  299. * @param[in] userList list to specify all users pointer to a list of type shellmatta_user_t
  300. * @param[in] userListLength length of the user list
  301. * @param[in] permList list to specify the command permissions pointer to a list of type shellmatta_perm_t
  302. * @param[in] permListLength length of the perm list
  303. * @param[in] customLogin true: no internal login command is provided
  304. * @param[in] checkFct pointer to custom credential check function of type shellmatta_check_t
  305. * @param[in] logFct pointer to login log function of type shellmatta_auth_log_t
  306. * @return errorcode #SHELLMATTA_OK
  307. * #SHELLMATTA_DUPLICATE (login/logout commands already existed)
  308. * #SHELLMATTA_USE_FAULT (param err)
  309. */
  310. shellmatta_retCode_t shellmatta_auth_init(shellmatta_handle_t handle,
  311. shellmatta_auth_user_t *userList,
  312. uint32_t userListLength,
  313. shellmatta_auth_perm_t *permList,
  314. uint32_t permListLength,
  315. bool customLogin,
  316. shellmatta_auth_check_t checkFct,
  317. shellmatta_auth_log_t logFct)
  318. {
  319. shellmatta_retCode_t ret = SHELLMATTA_OK;
  320. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  321. uint32_t i;
  322. shellmatta_cmd_t *cmd;
  323. /** -# check parameters for plausibility */
  324. if( (NULL != inst)
  325. && (SHELLMATTA_MAGIC == inst->magic)
  326. && (NULL != userList)
  327. && (0 != userListLength)
  328. && (NULL != permList)
  329. && (0 != permListLength))
  330. {
  331. inst->loginState = SHELLMATTA_AUTH_IDLE;
  332. inst->userId = 0u;
  333. inst->tmpUserId = 0u;
  334. inst->userPointer = NULL;
  335. inst->userList = userList;
  336. inst->userListLength = userListLength;
  337. inst->permList = permList;
  338. inst->permListLength = permListLength;
  339. SHELLMATTA_RET(ret, shellmatta_addCmd(handle, &inst->logoutCmd));
  340. if(!customLogin)
  341. {
  342. SHELLMATTA_RET(ret, shellmatta_addCmd(handle, &inst->loginCmd));
  343. }
  344. inst->checkFct = checkFct;
  345. inst->logFct = logFct;
  346. /** -# assign links to the perm list to each command */
  347. cmd = inst->cmdList;
  348. /** -# search for a matching command */
  349. while (NULL != cmd)
  350. {
  351. /** -# Search for command in perm list */
  352. for (i = 0u; i < permListLength; i++)
  353. {
  354. if (0 == strcmp(cmd->cmd, permList[i].cmd))
  355. {
  356. cmd->authLink = &permList[i];
  357. break;
  358. }
  359. }
  360. cmd = cmd->next;
  361. }
  362. }
  363. else
  364. {
  365. ret = SHELLMATTA_USE_FAULT;
  366. }
  367. return ret;
  368. }
  369. /**
  370. * @brief logs a passed user id into the shellmatta instance
  371. * @param[in] handle shellmatta instance handle
  372. * @param[in] userId userId to login
  373. * @return errorcode #SHELLMATTA_OK
  374. * #SHELLMATTA_ERR (user does not exist)
  375. * #SHELLMATTA_USE_FAULT (param err)
  376. */
  377. shellmatta_retCode_t shellmatta_auth_login(shellmatta_handle_t handle, uint32_t userId)
  378. {
  379. shellmatta_retCode_t ret = SHELLMATTA_OK;
  380. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  381. uint32_t i;
  382. /** -# check parameters for plausibility */
  383. if( (NULL != inst)
  384. && (SHELLMATTA_MAGIC == inst->magic))
  385. {
  386. /** -# log passed user id into the instance */
  387. inst->userId = userId;
  388. inst->userPointer = NULL;
  389. if (0 != userId)
  390. {
  391. /** -# set user pointer to print the name in the prompt */
  392. for (i = 0u; i < inst->userListLength; i++)
  393. {
  394. if (inst->userList[i].userId == userId)
  395. {
  396. inst->userPointer = &inst->userList[i];
  397. break;
  398. }
  399. }
  400. }
  401. /** -# check if the user did exist */
  402. if (NULL == inst->userPointer)
  403. {
  404. ret = SHELLMATTA_ERROR;
  405. }
  406. /** -# call log function */
  407. if (inst->logFct)
  408. {
  409. inst->logFct(userId,
  410. NULL != inst->userPointer ? \
  411. SHELLMATTA_AUTH_EVENT_LOGIN : \
  412. SHELLMATTA_AUTH_EVENT_LOGIN_FAILED);
  413. }
  414. }
  415. else
  416. {
  417. ret = SHELLMATTA_USE_FAULT;
  418. }
  419. return ret;
  420. }
  421. /**
  422. * @brief logs the currently logged in user out of the instance
  423. * @param[in] handle shellmatta instance handle
  424. * @return errorcode #SHELLMATTA_OK
  425. * #SHELLMATTA_USE_FAULT (param err)
  426. */
  427. shellmatta_retCode_t shellmatta_auth_logout(shellmatta_handle_t handle)
  428. {
  429. shellmatta_retCode_t ret = SHELLMATTA_OK;
  430. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  431. /** -# check parameters for plausibility */
  432. if( (NULL != inst)
  433. && (SHELLMATTA_MAGIC == inst->magic))
  434. {
  435. inst->userId = 0u;
  436. inst->userPointer = NULL;
  437. /** -# clear the history buffer */
  438. (void)history_clear(handle);
  439. }
  440. else
  441. {
  442. ret = SHELLMATTA_USE_FAULT;
  443. }
  444. return ret;
  445. }
  446. /**
  447. * @brief returns the currently logged in userId
  448. * @param[in] handle shellmatta instance handle
  449. * @return userId or 0 if no user is logged in
  450. */
  451. uint32_t shellmatta_auth_getLoggedInUserId(shellmatta_handle_t handle)
  452. {
  453. uint32_t userId = 0u;
  454. const shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  455. /** -# check parameters for plausibility */
  456. if( (NULL != inst)
  457. && (SHELLMATTA_MAGIC == inst->magic))
  458. {
  459. userId = inst->userId;
  460. }
  461. return userId;
  462. }
  463. /**
  464. * @brief copies the username into the passed buffer
  465. * @param[in] handle shellmatta instance handle
  466. * @param[out] data pointer to buffer to write the username into
  467. * @param[in, out] length in - length of the passed buffer, out - actual stringlength of the username
  468. * @return errorcode #SHELLMATTA_OK
  469. * #SHELLMATTA_ERROR (passed buffer is too small)
  470. * #SHELLMATTA_USE_FAULT (param err)
  471. */
  472. shellmatta_retCode_t shellmatta_auth_getLoggedInUserName(shellmatta_handle_t handle, char *data, uint32_t *length)
  473. {
  474. shellmatta_retCode_t ret = SHELLMATTA_OK;
  475. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  476. size_t userNameLength;
  477. /** -# check parameters for plausibility */
  478. if( (NULL != inst)
  479. && (SHELLMATTA_MAGIC == inst->magic)
  480. && (data != NULL)
  481. && (length != NULL))
  482. {
  483. if (NULL != inst->userPointer)
  484. {
  485. userNameLength = strlen(inst->userPointer->username);
  486. if (userNameLength < *length)
  487. {
  488. (void)strcpy(data, inst->userPointer->username);
  489. *length = userNameLength;
  490. }
  491. else
  492. {
  493. ret = SHELLMATTA_ERROR;
  494. }
  495. }
  496. }
  497. else
  498. {
  499. ret = SHELLMATTA_USE_FAULT;
  500. }
  501. return ret;
  502. }
  503. /**
  504. * @brief changes the password of the passed username
  505. * @param[in] handle shellmatta instance handle
  506. * @param[in] username username to change the password of
  507. * @param[in] password password to be set for the user
  508. * @return errorcode #SHELLMATTA_OK
  509. * #SHELLMATTA_ERROR (user not found)
  510. * #SHELLMATTA_USE_FAULT (param err)
  511. */
  512. shellmatta_retCode_t shellmatta_auth_chpasswd(shellmatta_handle_t handle, const char *username, const char *password)
  513. {
  514. shellmatta_retCode_t ret = SHELLMATTA_ERROR;
  515. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  516. uint32_t i;
  517. /** -# check parameters for plausibility */
  518. if( (NULL != inst)
  519. && (SHELLMATTA_MAGIC == inst->magic)
  520. && (username != NULL)
  521. && (password != NULL))
  522. {
  523. for (i = 0u; i < inst->userListLength; i++)
  524. {
  525. /** -# search for user */
  526. if (0 == strcmp(inst->userList[i].username, username))
  527. {
  528. inst->userList[i].password = password;
  529. return SHELLMATTA_OK;
  530. }
  531. }
  532. }
  533. else
  534. {
  535. ret = SHELLMATTA_USE_FAULT;
  536. }
  537. return ret;
  538. }
  539. /**
  540. * @brief checks if a command is accessible by the logged in user
  541. * @param[in] inst shellmatta instance to work with
  542. * @param[in] cmd command to check permissions for
  543. * @return errorcode #SHELLMATTA_OK => permitted
  544. * #SHELLMATTA_ERROR => not permitted
  545. */
  546. shellmatta_retCode_t shellmatta_auth_is_cmd_permitted(const shellmatta_instance_t *inst, shellmatta_cmd_t *cmd)
  547. {
  548. shellmatta_retCode_t ret = SHELLMATTA_ERROR;
  549. shellmatta_auth_perm_t *permList;
  550. uint32_t i;
  551. /**! -# commands without an authentication set are public */
  552. if (NULL == cmd->authLink)
  553. {
  554. ret = SHELLMATTA_OK;
  555. }
  556. /**! -# allow superuser to access all commands */
  557. else if ((NULL != inst->userPointer) && (true == inst->userPointer->superuser))
  558. {
  559. ret = SHELLMATTA_OK;
  560. }
  561. else
  562. {
  563. permList = cmd->authLink;
  564. for (i = 0u; i < permList->userIdslength; i++)
  565. {
  566. if (inst->userId == permList->userIds[i])
  567. {
  568. ret = SHELLMATTA_OK;
  569. break;
  570. }
  571. }
  572. }
  573. return ret;
  574. }
  575. /**
  576. * @}
  577. */