cmd_pwd.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. /*
  2. *******************************************************************************
  3. \file cmd_pwd.c
  4. \brief Command-line interface to Bee2: password management
  5. \project bee2/cmd
  6. \created 2022.06.13
  7. \version 2024.06.14
  8. \copyright The Bee2 authors
  9. \license Licensed under the Apache License, Version 2.0 (see LICENSE.txt).
  10. *******************************************************************************
  11. */
  12. #include "../cmd.h"
  13. #include <bee2/core/blob.h>
  14. #include <bee2/core/dec.h>
  15. #include <bee2/core/err.h>
  16. #include <bee2/core/hex.h>
  17. #include <bee2/core/mem.h>
  18. #include <bee2/core/rng.h>
  19. #include <bee2/core/str.h>
  20. #include <bee2/core/util.h>
  21. #include <bee2/crypto/belt.h>
  22. #include <bee2/crypto/bels.h>
  23. #include <bee2/crypto/bpki.h>
  24. #include <bee2/crypto/brng.h>
  25. #include <stdio.h>
  26. #include <stdlib.h>
  27. /*
  28. *******************************************************************************
  29. Управление паролями: базовые функции
  30. *******************************************************************************
  31. */
  32. cmd_pwd_t cmdPwdCreate(size_t size)
  33. {
  34. return (cmd_pwd_t)blobCreate(size + 1);
  35. }
  36. bool_t cmdPwdIsValid(const cmd_pwd_t pwd)
  37. {
  38. return pwd != 0 && blobIsValid(pwd) && strIsValid(pwd) &&
  39. pwd[blobSize(pwd) - 1] == '\0';
  40. }
  41. void cmdPwdClose(cmd_pwd_t pwd)
  42. {
  43. ASSERT(pwd == 0 || cmdPwdIsValid(pwd));
  44. blobClose(pwd);
  45. }
  46. /*
  47. *******************************************************************************
  48. Управление паролями: самотестирование
  49. *******************************************************************************
  50. */
  51. err_t pwdSelfTest()
  52. {
  53. const char pwd[] = "B194BAC80A08F53B";
  54. octet stack[1024];
  55. octet buf[5 * (32 + 1)];
  56. octet buf1[32];
  57. // bels-share: разделение и сборка
  58. if (belsShare3(buf, 5, 3, 32, beltH()) != ERR_OK)
  59. return ERR_SELFTEST;
  60. if (belsRecover2(buf1, 1, 32, buf) != ERR_OK ||
  61. memEq(buf1, beltH(), 32))
  62. return ERR_SELFTEST;
  63. if (belsRecover2(buf1, 2, 32, buf) != ERR_OK ||
  64. memEq(buf1, beltH(), 32))
  65. return ERR_SELFTEST;
  66. if (belsRecover2(buf1, 3, 32, buf) != ERR_OK ||
  67. !memEq(buf1, beltH(), 32))
  68. return ERR_SELFTEST;
  69. // brng-ctr: тест Б.2
  70. ASSERT(sizeof(stack) >= brngCTR_keep());
  71. memCopy(buf, beltH(), 96);
  72. brngCTRStart(stack, beltH() + 128, beltH() + 128 + 64);
  73. brngCTRStepR(buf, 96, stack);
  74. if (!hexEq(buf,
  75. "1F66B5B84B7339674533F0329C74F218"
  76. "34281FED0732429E0C79235FC273E269"
  77. "4C0E74B2CD5811AD21F23DE7E0FA742C"
  78. "3ED6EC483C461CE15C33A77AA308B7D2"
  79. "0F51D91347617C20BD4AB07AEF4F26A1"
  80. "AD1362A8F9A3D42FBE1B8E6F1C88AAD5"))
  81. return ERR_SELFTEST;
  82. // pbkdf2 тест E.5
  83. beltPBKDF2(buf, (const octet*)"B194BAC80A08F53B", strLen(pwd), 10000,
  84. beltH() + 128 + 64, 8);
  85. if (!hexEq(buf,
  86. "3D331BBBB1FBBB40E4BF22F6CB9A689E"
  87. "F13A77DC09ECF93291BFE42439A72E7D"))
  88. return FALSE;
  89. // belt-kwp: тест A.21
  90. ASSERT(sizeof(stack) >= beltKWP_keep());
  91. beltKWPStart(stack, beltH() + 128, 32);
  92. memCopy(buf, beltH(), 32);
  93. memCopy(buf + 32, beltH() + 32, 16);
  94. beltKWPStepE(buf, 48, stack);
  95. if (!hexEq(buf,
  96. "49A38EE108D6C742E52B774F00A6EF98"
  97. "B106CBD13EA4FB0680323051BC04DF76"
  98. "E487B055C69BCF541176169F1DC9F6C8"))
  99. return FALSE;
  100. // все нормально
  101. return ERR_OK;
  102. }
  103. /*
  104. *******************************************************************************
  105. Управление паролями: схема pass
  106. *******************************************************************************
  107. */
  108. static err_t cmdPwdGenPass(cmd_pwd_t* pwd, const char* cmdline)
  109. {
  110. return ERR_NOT_IMPLEMENTED;
  111. }
  112. static err_t cmdPwdReadPass(cmd_pwd_t* pwd, const char* cmdline)
  113. {
  114. ASSERT(memIsValid(pwd, sizeof(cmd_pwd_t)));
  115. ASSERT(strIsValid(cmdline));
  116. // создать пароль
  117. if (!(*pwd = cmdPwdCreate(strLen(cmdline))))
  118. return ERR_OUTOFMEMORY;
  119. strCopy(*pwd, cmdline);
  120. return ERR_OK;
  121. }
  122. /*
  123. *******************************************************************************
  124. Управление паролями: схема env
  125. *******************************************************************************
  126. */
  127. static const char* cmdEnvGet(const char* name)
  128. {
  129. const char* val;
  130. val = getenv(name);
  131. return strIsValid(val) ? val : 0;
  132. }
  133. static err_t cmdPwdGenEnv(cmd_pwd_t* pwd, const char* cmdline)
  134. {
  135. return ERR_NOT_IMPLEMENTED;
  136. }
  137. static err_t cmdPwdReadEnv(cmd_pwd_t* pwd, const char* cmdline)
  138. {
  139. const char* val;
  140. // pre
  141. ASSERT(memIsValid(pwd, sizeof(cmd_pwd_t)));
  142. ASSERT(strIsValid(cmdline));
  143. // читать пароль из переменной окружения
  144. if (!(val = cmdEnvGet(cmdline)))
  145. return ERR_BAD_ENV;
  146. // возвратить пароль
  147. if (!(*pwd = cmdPwdCreate(strLen(val))))
  148. return ERR_OUTOFMEMORY;
  149. strCopy(*pwd, val);
  150. return ERR_OK;
  151. }
  152. /*
  153. *******************************************************************************
  154. Управление паролями: схема share
  155. *******************************************************************************
  156. */
  157. static err_t cmdPwdGenShare_internal(cmd_pwd_t* pwd, size_t scount,
  158. size_t threshold, size_t len, bool_t crc, char* shares[],
  159. const cmd_pwd_t spwd)
  160. {
  161. err_t code;
  162. const size_t iter = 10000;
  163. size_t epki_len;
  164. void* stack;
  165. octet* pwd_bin;
  166. octet* state;
  167. octet* share;
  168. octet* salt;
  169. octet* epki;
  170. // pre
  171. ASSERT(memIsValid(pwd, sizeof(cmd_pwd_t)));
  172. ASSERT(cmdPwdIsValid(spwd));
  173. ASSERT(2 <= scount && scount <= 16);
  174. ASSERT(2 <= threshold && threshold <= scount);
  175. ASSERT(len % 8 == 0 && len <= 32);
  176. ASSERT(!crc || len != 16);
  177. // пароль пока не создан
  178. *pwd = 0;
  179. // определить длину пароля
  180. if (len == 0)
  181. len = 32;
  182. // определить длину контейнера с частичным секретом
  183. code = bpkiShareWrap(0, &epki_len, 0, len + 1, 0, 0, 0, iter);
  184. ERR_CALL_CHECK(code);
  185. // запустить ГСЧ
  186. code = cmdRngStart(TRUE);
  187. ERR_CALL_CHECK(code);
  188. // выделить память и разметить ее
  189. code = cmdBlobCreate(stack, len +
  190. utilMax(2,
  191. beltMAC_keep(),
  192. scount * (len + 1) + epki_len + 8));
  193. ERR_CALL_CHECK(code);
  194. pwd_bin = (octet*)stack;
  195. state = share = pwd_bin + len;
  196. salt = share + scount * (len + 1);
  197. epki = salt + 8;
  198. // генерировать пароль
  199. if (crc)
  200. {
  201. rngStepR(pwd_bin, len - 8, 0);
  202. beltMACStart(state, pwd_bin, len - 8);
  203. beltMACStepA(pwd_bin, len - 8, state);
  204. beltMACStepG(pwd_bin + len - 8, state);
  205. }
  206. else
  207. rngStepR(pwd_bin, len, 0);
  208. // разделить пароль на частичные секреты
  209. code = belsShare2(share, scount, threshold, len, pwd_bin, rngStepR, 0);
  210. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  211. // обновить ключ ГСЧ
  212. rngRekey();
  213. // защитить частичные секреты
  214. for (; scount--; share += (len + 1), ++shares)
  215. {
  216. // установить защиту
  217. rngStepR(salt, 8, 0);
  218. code = bpkiShareWrap(epki, 0, share, len + 1, (const octet*)spwd,
  219. cmdPwdLen(spwd), salt, iter);
  220. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  221. // записать в файл
  222. code = cmdFileWrite(*shares, epki, epki_len);
  223. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  224. }
  225. // создать выходной (текстовый) пароль
  226. *pwd = cmdPwdCreate(2 * len);
  227. code = *pwd ? ERR_OK : ERR_OUTOFMEMORY;
  228. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  229. hexFrom(*pwd, pwd_bin, len);
  230. cmdBlobClose(stack);
  231. return code;
  232. }
  233. static err_t cmdPwdReadShare_internal(cmd_pwd_t* pwd, size_t scount,
  234. size_t len, bool_t crc, char* shares[], const cmd_pwd_t spwd)
  235. {
  236. err_t code;
  237. size_t epki_len;
  238. size_t epki_len_min;
  239. size_t epki_len_max;
  240. void* stack;
  241. octet* share;
  242. octet* state;
  243. octet* epki;
  244. octet* pwd_bin;
  245. size_t pos;
  246. // pre
  247. ASSERT(memIsValid(pwd, sizeof(cmd_pwd_t)));
  248. ASSERT(cmdPwdIsValid(spwd));
  249. ASSERT(2 <= scount && scount <= 16);
  250. ASSERT(len % 8 == 0 && len <= 32);
  251. ASSERT(!crc || len != 16);
  252. // пароль пока не создан
  253. *pwd = 0;
  254. // определить длину частичного секрета
  255. if (len == 0)
  256. {
  257. // определить размер первого файла с частичным секретом
  258. if ((epki_len = cmdFileSize(shares[0])) == SIZE_MAX)
  259. return ERR_FILE_READ;
  260. // найти подходящую длину
  261. for (len = 16; len <= 32; len += 8)
  262. {
  263. code = bpkiShareWrap(0, &epki_len_min, 0, len + 1, 0, 0, 0, 10000);
  264. ERR_CALL_CHECK(code);
  265. code = bpkiShareWrap(0, &epki_len_max, 0, len + 1, 0, 0, 0,
  266. SIZE_MAX);
  267. ERR_CALL_CHECK(code);
  268. if (epki_len_min <= epki_len && epki_len <= epki_len_max)
  269. break;
  270. }
  271. if (len > 32)
  272. return ERR_BAD_FORMAT;
  273. }
  274. else
  275. {
  276. code = bpkiShareWrap(0, &epki_len_min, 0, len + 1, 0, 0, 0, 10000);
  277. ERR_CALL_CHECK(code);
  278. code = bpkiShareWrap(0, &epki_len_max, 0, len + 1, 0, 0, 0, SIZE_MAX);
  279. ERR_CALL_CHECK(code);
  280. }
  281. // выделить память и разметить ее
  282. code = cmdBlobCreate(stack, scount * (len + 1) + epki_len_max + 1 + len);
  283. ERR_CALL_HANDLE(code, cmdPwdClose(*pwd));
  284. share = state = (octet*)stack;
  285. epki = share + scount * (len + 1);
  286. pwd_bin = epki + epki_len_max + 1;
  287. // прочитать частичные секреты
  288. for (pos = 0; pos < scount; ++pos, ++shares)
  289. {
  290. size_t share_len;
  291. // определить длину контейнера
  292. code = cmdFileReadAll(0, &epki_len, *shares);
  293. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  294. // проверить длину
  295. code = (epki_len_min <= epki_len && epki_len <= epki_len_max) ?
  296. ERR_OK : ERR_BAD_FORMAT;
  297. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  298. // читать
  299. code = cmdFileReadAll(epki, &epki_len, *shares);
  300. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  301. // декодировать
  302. code = bpkiShareUnwrap(share + pos * (len + 1), &share_len,
  303. epki, epki_len, (const octet*)spwd, cmdPwdLen(spwd));
  304. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  305. code = (share_len == len + 1) ? ERR_OK : ERR_BAD_FORMAT;
  306. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  307. }
  308. // собрать пароль
  309. code = belsRecover2(pwd_bin, scount, len, share);
  310. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  311. // проверить пароль
  312. if (crc)
  313. {
  314. beltMACStart(state, pwd_bin, len - 8);
  315. beltMACStepA(pwd_bin, len - 8, state);
  316. if (!beltMACStepV(pwd_bin + len - 8, state))
  317. code = ERR_BAD_CRC;
  318. }
  319. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  320. // создать выходной (текстовый) пароль
  321. *pwd = cmdPwdCreate(2 * len);
  322. code = *pwd ? ERR_OK : ERR_OUTOFMEMORY;
  323. ERR_CALL_HANDLE(code, cmdBlobClose(stack));
  324. hexFrom(*pwd, pwd_bin, len);
  325. cmdBlobClose(stack);
  326. return code;
  327. }
  328. static err_t cmdPwdGenShare(cmd_pwd_t* pwd, const char* cmdline)
  329. {
  330. err_t code;
  331. int argc;
  332. char** argv = 0;
  333. size_t offset = 0;
  334. size_t threshold = 0;
  335. size_t len = 0;
  336. bool_t crc = FALSE;
  337. cmd_pwd_t spwd = 0;
  338. // составить список аргументов
  339. code = cmdArgCreate(&argc, &argv, cmdline);
  340. ERR_CALL_CHECK(code);
  341. // обработать опции
  342. while (argc && strStartsWith(argv[offset], "-"))
  343. {
  344. // порог
  345. if (strStartsWith(argv[offset], "-t"))
  346. {
  347. char* str = argv[offset] + strLen("-t");
  348. if (threshold)
  349. {
  350. code = ERR_CMD_DUPLICATE;
  351. goto final;
  352. }
  353. if (!decIsValid(str) || decCLZ(str) || strLen(str) > 2 ||
  354. (threshold = (size_t)decToU32(str)) < 2 || threshold > 16)
  355. {
  356. code = ERR_CMD_PARAMS;
  357. goto final;
  358. }
  359. ++offset, --argc;
  360. }
  361. // уровень стойкости
  362. else if (strStartsWith(argv[offset], "-l"))
  363. {
  364. char* str = argv[offset] + strLen("-l");
  365. if (len)
  366. {
  367. code = ERR_CMD_DUPLICATE;
  368. goto final;
  369. }
  370. if (!decIsValid(str) || decCLZ(str) || strLen(str) != 3 ||
  371. (len = (size_t)decToU32(str)) % 64 || len < 128 || len > 256)
  372. {
  373. code = ERR_CMD_PARAMS;
  374. goto final;
  375. }
  376. len /= 8;
  377. if (len == 16 && crc)
  378. {
  379. code = ERR_CMD_PARAMS;
  380. goto final;
  381. }
  382. ++offset, --argc;
  383. }
  384. // контрольная сумма
  385. else if (strStartsWith(argv[offset], "-crc"))
  386. {
  387. if (crc)
  388. {
  389. code = ERR_CMD_DUPLICATE;
  390. goto final;
  391. }
  392. if (len == 16)
  393. {
  394. code = ERR_CMD_PARAMS;
  395. goto final;
  396. }
  397. crc = TRUE, ++offset, --argc;
  398. }
  399. // пароль защиты частичных секретов
  400. else if (strEq(argv[offset], "-pass"))
  401. {
  402. if (spwd)
  403. {
  404. code = ERR_CMD_DUPLICATE;
  405. goto final;
  406. }
  407. ++offset, --argc;
  408. // определить пароль защиты частичных секретов
  409. code = cmdPwdRead(&spwd, argv[offset]);
  410. ERR_CALL_HANDLE(code, cmdArgClose(argv));
  411. ASSERT(cmdPwdIsValid(spwd));
  412. ++offset, --argc;
  413. }
  414. else
  415. {
  416. code = ERR_CMD_PARAMS;
  417. goto final;
  418. }
  419. }
  420. // проверить, что пароль защиты частичных секретов построен
  421. if (!spwd)
  422. {
  423. code = ERR_CMD_PARAMS;
  424. goto final;
  425. }
  426. // настроить порог
  427. if (!threshold)
  428. threshold = 2;
  429. // проверить число файлов с частичными секретами
  430. if ((size_t)argc < threshold)
  431. {
  432. code = ERR_CMD_PARAMS;
  433. goto final;
  434. }
  435. // проверить отсутствие файлов с частичными секретами
  436. if ((code = cmdFileValNotExist(argc, argv + offset)) != ERR_OK)
  437. goto final;
  438. // построить пароль
  439. code = cmdPwdGenShare_internal(pwd, (size_t)argc, threshold, len, crc,
  440. argv + offset, spwd);
  441. final:
  442. cmdPwdClose(spwd);
  443. cmdArgClose(argv);
  444. return code;
  445. }
  446. static err_t cmdPwdReadShare(cmd_pwd_t* pwd, const char* cmdline)
  447. {
  448. err_t code;
  449. int argc;
  450. char** argv = 0;
  451. size_t offset = 0;
  452. size_t threshold = 0;
  453. size_t len = 0;
  454. bool_t crc = FALSE;
  455. cmd_pwd_t spwd = 0;
  456. // составить список аргументов
  457. code = cmdArgCreate(&argc, &argv, cmdline);
  458. ERR_CALL_CHECK(code);
  459. // обработать опции
  460. while (argc && strStartsWith(argv[offset], "-"))
  461. {
  462. // порог
  463. if (strStartsWith(argv[offset], "-t"))
  464. {
  465. char* str = argv[offset] + strLen("-t");
  466. if (threshold)
  467. {
  468. code = ERR_CMD_DUPLICATE;
  469. goto final;
  470. }
  471. if (!decIsValid(str) || decCLZ(str) || strLen(str) > 2 ||
  472. (threshold = (size_t)decToU32(str)) < 2 || threshold > 16)
  473. {
  474. code = ERR_CMD_PARAMS;
  475. goto final;
  476. }
  477. ++offset, --argc;
  478. }
  479. // уровень стойкости
  480. else if (strStartsWith(argv[offset], "-l"))
  481. {
  482. char* str = argv[offset] + strLen("-l");
  483. if (len)
  484. {
  485. code = ERR_CMD_DUPLICATE;
  486. goto final;
  487. }
  488. if (!decIsValid(str) || decCLZ(str) || strLen(str) != 3 ||
  489. (len = (size_t)decToU32(str)) % 64 || len < 128 || len > 256)
  490. {
  491. code = ERR_CMD_PARAMS;
  492. goto final;
  493. }
  494. len /= 8;
  495. if (len == 16 && crc)
  496. {
  497. code = ERR_CMD_PARAMS;
  498. goto final;
  499. }
  500. ++offset, --argc;
  501. }
  502. // контрольная сумма
  503. else if (strStartsWith(argv[offset], "-crc"))
  504. {
  505. if (crc)
  506. {
  507. code = ERR_CMD_DUPLICATE;
  508. goto final;
  509. }
  510. if (len == 16)
  511. {
  512. code = ERR_CMD_PARAMS;
  513. goto final;
  514. }
  515. crc = TRUE, ++offset, --argc;
  516. }
  517. // пароль защиты частичных секретов
  518. else if (strEq(argv[offset], "-pass"))
  519. {
  520. if (spwd)
  521. {
  522. code = ERR_CMD_DUPLICATE;
  523. goto final;
  524. }
  525. ++offset, --argc;
  526. // определить пароль защиты частичных секретов
  527. code = cmdPwdRead(&spwd, argv[offset]);
  528. ERR_CALL_HANDLE(code, cmdArgClose(argv));
  529. ASSERT(cmdPwdIsValid(spwd));
  530. ++offset, --argc;
  531. }
  532. else
  533. {
  534. code = ERR_CMD_PARAMS;
  535. goto final;
  536. }
  537. }
  538. // проверить, что пароль защиты частичных секретов определен
  539. if (!spwd)
  540. {
  541. code = ERR_CMD_PARAMS;
  542. goto final;
  543. }
  544. // настроить порог
  545. if (!threshold)
  546. threshold = 2;
  547. // проверить число файлов с частичными секретами
  548. if ((size_t)argc < threshold)
  549. {
  550. code = ERR_CMD_PARAMS;
  551. goto final;
  552. }
  553. // проверить наличие файлов с частичными секретами
  554. if ((code = cmdFileValExist(argc, argv + offset)) != ERR_OK)
  555. goto final;
  556. // определить пароль
  557. code = cmdPwdReadShare_internal(pwd, (size_t)argc, len, crc,
  558. argv + offset, spwd);
  559. final:
  560. cmdPwdClose(spwd);
  561. cmdArgClose(argv);
  562. return code;
  563. }
  564. /*
  565. *******************************************************************************
  566. Управление паролями: построение / определение
  567. *******************************************************************************
  568. */
  569. err_t cmdPwdGen(cmd_pwd_t* pwd, const char* cmdline)
  570. {
  571. if (strStartsWith(cmdline, "pass:"))
  572. return cmdPwdGenPass(pwd, cmdline + strLen("pass:"));
  573. else if (strStartsWith(cmdline, "env:"))
  574. return cmdPwdGenEnv(pwd, cmdline + strLen("env:"));
  575. else if (strStartsWith(cmdline, "share:"))
  576. return cmdPwdGenShare(pwd, cmdline + strLen("share:"));
  577. return ERR_CMD_PARAMS;
  578. }
  579. err_t cmdPwdRead(cmd_pwd_t* pwd, const char* cmdline)
  580. {
  581. if (strStartsWith(cmdline, "pass:"))
  582. return cmdPwdReadPass(pwd, cmdline + strLen("pass:"));
  583. else if (strStartsWith(cmdline, "env:"))
  584. return cmdPwdReadEnv(pwd, cmdline + strLen("env:"));
  585. else if (strStartsWith(cmdline, "share:"))
  586. return cmdPwdReadShare(pwd, cmdline + strLen("share:"));
  587. return ERR_CMD_PARAMS;
  588. }