tpmc.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  2. * Use of this source code is governed by a BSD-style license that can be
  3. * found in the LICENSE file.
  4. *
  5. * TPM command utility. Runs simple TPM commands. Mostly useful when physical
  6. * presence has not been locked.
  7. *
  8. * The exit code is 0 for success, the TPM error code for TPM errors, and 255
  9. * for other errors.
  10. */
  11. #include <stdint.h>
  12. #include <stdlib.h>
  13. #include <stdio.h>
  14. #include <string.h>
  15. #include <syslog.h>
  16. #include "tlcl.h"
  17. #include "tpm_error_messages.h"
  18. #include "tss_constants.h"
  19. #define OTHER_ERROR 255 /* OTHER_ERROR must be the largest uint8_t value. */
  20. #ifdef TPM2_MODE
  21. #define TPM_MODE_SELECT(_, tpm20_ver) tpm20_ver
  22. #else
  23. #define TPM_MODE_SELECT(tpm12_ver, _) tpm12_ver
  24. #endif
  25. #define TPM_MODE_STRING TPM_MODE_SELECT("1.2", "2.0")
  26. #define TPM12_NEEDS_PP TPM_MODE_SELECT(" (needs PP)", "")
  27. #define TPM12_NEEDS_PP_REBOOT TPM_MODE_SELECT(" (needs PP, maybe reboot)", "")
  28. #define TPM20_NOT_IMPLEMENTED_DESCR(descr) \
  29. descr TPM_MODE_SELECT("", " [not-implemented for TPM2.0]")
  30. #define TPM20_NOT_IMPLEMENTED_HANDLER(handler) \
  31. TPM_MODE_SELECT(handler, HandlerNotImplementedForTPM2)
  32. #define TPM20_NOT_IMPLEMENTED(descr, handler) \
  33. TPM20_NOT_IMPLEMENTED_DESCR(descr), \
  34. TPM20_NOT_IMPLEMENTED_HANDLER(handler)
  35. #define TPM20_DOES_NOTHING_DESCR(descr) \
  36. descr TPM_MODE_SELECT("", " [no-op for TPM2.0]")
  37. #define TPM20_DOES_NOTHING_HANDLER(handler) \
  38. TPM_MODE_SELECT(handler, HandlerDoNothingForTPM2)
  39. #define TPM20_DOES_NOTHING(descr, handler) \
  40. TPM20_DOES_NOTHING_DESCR(descr), \
  41. TPM20_DOES_NOTHING_HANDLER(handler)
  42. typedef struct command_record {
  43. const char* name;
  44. const char* abbr;
  45. const char* description;
  46. uint32_t (*handler)(void);
  47. } command_record;
  48. /* Set in main, consumed by handler functions below. We use global variables
  49. * so we can also choose to call Tlcl*() functions directly; they don't take
  50. * argv/argc.
  51. */
  52. int nargs;
  53. char** args;
  54. /* Converts a string in the form 0x[0-9a-f]+ to a 32-bit value. Returns 0 for
  55. * success, non-zero for failure.
  56. */
  57. int HexStringToUint32(const char* string, uint32_t* value) {
  58. char tail[1];
  59. /* strtoul is not as good because it overflows silently */
  60. char* format = strncmp(string, "0x", 2) ? "%8x%s" : "0x%8x%s";
  61. int n = sscanf(string, format, value, tail);
  62. return n != 1;
  63. }
  64. /* Converts a string in the form [0-9a-f]+ to an 8-bit value. Returns 0 for
  65. * success, non-zero for failure.
  66. */
  67. int HexStringToUint8(const char* string, uint8_t* value) {
  68. char* end;
  69. uint32_t large_value = strtoul(string, &end, 16);
  70. if (*end != '\0' || large_value > 0xff) {
  71. return 1;
  72. }
  73. *value = large_value;
  74. return 0;
  75. }
  76. int HexStringToArray(const char* string, uint8_t* value, int num_bytes) {
  77. int len = strlen(string);
  78. if (!strncmp(string, "0x", 2)) {
  79. string += 2;
  80. len -= 2;
  81. }
  82. if (len != num_bytes * 2) {
  83. return 1;
  84. }
  85. for (; len > 0; string += 2, len -= 2, value++) {
  86. if (sscanf(string, "%2hhx", value) != 1) {
  87. return 1;
  88. }
  89. }
  90. return 0;
  91. }
  92. /* TPM error check and reporting. Returns 0 if |result| is 0 (TPM_SUCCESS).
  93. * Otherwise looks up a TPM error in the error table and prints the error if
  94. * found. Then returns min(result, OTHER_ERROR) since some error codes, such
  95. * as TPM_E_RETRY, do not fit in a byte.
  96. */
  97. uint8_t ErrorCheck(uint32_t result, const char* cmd) {
  98. uint8_t exit_code = result > OTHER_ERROR ? OTHER_ERROR : result;
  99. if (result == 0) {
  100. return 0;
  101. } else {
  102. int i;
  103. int n = sizeof(tpm_error_table) / sizeof(tpm_error_table[0]);
  104. fprintf(stderr, "command \"%s\" failed with code 0x%x\n", cmd, result);
  105. for (i = 0; i < n; i++) {
  106. if (tpm_error_table[i].code == result) {
  107. fprintf(stderr, "%s\n%s\n", tpm_error_table[i].name,
  108. tpm_error_table[i].description);
  109. return exit_code;
  110. }
  111. }
  112. fprintf(stderr, "the TPM error code is unknown to this program\n");
  113. return exit_code;
  114. }
  115. }
  116. /* Handler functions. These wouldn't exist if C had closures.
  117. */
  118. static uint32_t HandlerTpmVersion(void) {
  119. puts(TPM_MODE_STRING);
  120. return 0;
  121. }
  122. /* TODO(apronin): stub for selected flags for TPM2 */
  123. #ifdef TPM2_MODE
  124. static uint32_t HandlerGetFlags(void) {
  125. fprintf(stderr, "getflags not implemented for TPM2\n");
  126. exit(OTHER_ERROR);
  127. }
  128. #else
  129. static uint32_t HandlerGetFlags(void) {
  130. uint8_t disabled;
  131. uint8_t deactivated;
  132. uint8_t nvlocked;
  133. uint32_t result = TlclGetFlags(&disabled, &deactivated, &nvlocked);
  134. if (result == 0) {
  135. printf("disabled: %d\ndeactivated: %d\nnvlocked: %d\n",
  136. disabled, deactivated, nvlocked);
  137. }
  138. return result;
  139. }
  140. #endif
  141. #ifndef TPM2_MODE
  142. static uint32_t HandlerActivate(void) {
  143. return TlclSetDeactivated(0);
  144. }
  145. static uint32_t HandlerDeactivate(void) {
  146. return TlclSetDeactivated(1);
  147. }
  148. #endif
  149. static uint32_t HandlerDefineSpace(void) {
  150. uint32_t index, size, perm;
  151. if (nargs != 5) {
  152. fprintf(stderr, "usage: tpmc def <index> <size> <perm>\n");
  153. exit(OTHER_ERROR);
  154. }
  155. if (HexStringToUint32(args[2], &index) != 0 ||
  156. HexStringToUint32(args[3], &size) != 0 ||
  157. HexStringToUint32(args[4], &perm) != 0) {
  158. fprintf(stderr, "<index>, <size>, and <perm> must be "
  159. "32-bit hex (0x[0-9a-f]+)\n");
  160. exit(OTHER_ERROR);
  161. }
  162. return TlclDefineSpace(index, perm, size);
  163. }
  164. static uint32_t HandlerWrite(void) {
  165. uint32_t index, size;
  166. uint8_t value[TPM_MAX_COMMAND_SIZE];
  167. char** byteargs;
  168. int i;
  169. if (nargs < 3) {
  170. fprintf(stderr, "usage: tpmc write <index> [<byte0> <byte1> ...]\n");
  171. exit(OTHER_ERROR);
  172. }
  173. if (HexStringToUint32(args[2], &index) != 0) {
  174. fprintf(stderr, "<index> must be 32-bit hex (0x[0-9a-f]+)\n");
  175. exit(OTHER_ERROR);
  176. }
  177. size = nargs - 3;
  178. if (size > sizeof(value)) {
  179. fprintf(stderr, "byte array too large\n");
  180. exit(OTHER_ERROR);
  181. }
  182. byteargs = args + 3;
  183. for (i = 0; i < size; i++) {
  184. if (HexStringToUint8(byteargs[i], &value[i]) != 0) {
  185. fprintf(stderr, "invalid byte %s, should be [0-9a-f][0-9a-f]?\n",
  186. byteargs[i]);
  187. exit(OTHER_ERROR);
  188. }
  189. }
  190. if (size == 0) {
  191. #ifndef TPM2_MODE
  192. if (index == TPM_NV_INDEX_LOCK) {
  193. fprintf(stderr, "This would set the nvLocked bit. "
  194. "Use \"tpmc setnv\" instead.\n");
  195. exit(OTHER_ERROR);
  196. }
  197. #endif
  198. printf("warning: zero-length write\n");
  199. } else {
  200. printf("writing %d byte%s\n", size, size > 1 ? "s" : "");
  201. }
  202. return TlclWrite(index, value, size);
  203. }
  204. static uint32_t HandlerPCRRead(void) {
  205. uint32_t index;
  206. uint8_t value[TPM_PCR_DIGEST];
  207. uint32_t result;
  208. int i;
  209. if (nargs != 3) {
  210. fprintf(stderr, "usage: tpmc pcrread <index>\n");
  211. exit(OTHER_ERROR);
  212. }
  213. if (HexStringToUint32(args[2], &index) != 0) {
  214. fprintf(stderr, "<index> must be 32-bit hex (0x[0-9a-f]+)\n");
  215. exit(OTHER_ERROR);
  216. }
  217. result = TlclPCRRead(index, value, sizeof(value));
  218. if (result == 0) {
  219. for (i = 0; i < TPM_PCR_DIGEST; i++) {
  220. printf("%02x", value[i]);
  221. }
  222. printf("\n");
  223. }
  224. return result;
  225. }
  226. static uint32_t HandlerPCRExtend(void) {
  227. uint32_t index;
  228. uint8_t value[TPM_PCR_DIGEST];
  229. if (nargs != 4) {
  230. fprintf(stderr, "usage: tpmc pcrextend <index> <extend_hash>\n");
  231. exit(OTHER_ERROR);
  232. }
  233. if (HexStringToUint32(args[2], &index) != 0) {
  234. fprintf(stderr, "<index> must be 32-bit hex (0x[0-9a-f]+)\n");
  235. exit(OTHER_ERROR);
  236. }
  237. if (HexStringToArray(args[3], value, TPM_PCR_DIGEST)) {
  238. fprintf(stderr, "<extend_hash> must be a 20-byte hex string\n");
  239. exit(OTHER_ERROR);
  240. }
  241. return TlclExtend(index, value, value);
  242. }
  243. static uint32_t HandlerRead(void) {
  244. uint32_t index, size;
  245. uint8_t value[4096];
  246. uint32_t result;
  247. int i;
  248. if (nargs != 4) {
  249. fprintf(stderr, "usage: tpmc read <index> <size>\n");
  250. exit(OTHER_ERROR);
  251. }
  252. if (HexStringToUint32(args[2], &index) != 0 ||
  253. HexStringToUint32(args[3], &size) != 0) {
  254. fprintf(stderr, "<index> and <size> must be 32-bit hex (0x[0-9a-f]+)\n");
  255. exit(OTHER_ERROR);
  256. }
  257. if (size > sizeof(value)) {
  258. fprintf(stderr, "size of read (0x%x) is too big\n", size);
  259. exit(OTHER_ERROR);
  260. }
  261. result = TlclRead(index, value, size);
  262. if (result == 0 && size > 0) {
  263. for (i = 0; i < size - 1; i++) {
  264. printf("%x ", value[i]);
  265. }
  266. printf("%x\n", value[i]);
  267. }
  268. return result;
  269. }
  270. static uint32_t HandlerGetPermissions(void) {
  271. uint32_t index, permissions, result;
  272. if (nargs != 3) {
  273. fprintf(stderr, "usage: tpmc getp <index>\n");
  274. exit(OTHER_ERROR);
  275. }
  276. if (HexStringToUint32(args[2], &index) != 0) {
  277. fprintf(stderr, "<index> must be 32-bit hex (0x[0-9a-f]+)\n");
  278. exit(OTHER_ERROR);
  279. }
  280. result = TlclGetPermissions(index, &permissions);
  281. if (result == 0) {
  282. printf("space 0x%x has permissions 0x%x\n", index, permissions);
  283. }
  284. return result;
  285. }
  286. static uint32_t HandlerGetOwnership(void) {
  287. uint8_t owned = 0;
  288. uint32_t result;
  289. if (nargs != 2) {
  290. fprintf(stderr, "usage: tpmc getownership\n");
  291. exit(OTHER_ERROR);
  292. }
  293. result = TlclGetOwnership(&owned);
  294. if (result == 0) {
  295. printf("Owned: %s\n", owned ? "yes" : "no");
  296. }
  297. return result;
  298. }
  299. static uint32_t HandlerGetRandom(void) {
  300. uint32_t length, size = 0;
  301. uint8_t* bytes;
  302. uint32_t result;
  303. int i;
  304. if (nargs != 3) {
  305. fprintf(stderr, "usage: tpmc getrandom <size>\n");
  306. exit(OTHER_ERROR);
  307. }
  308. if (HexStringToUint32(args[2], &length) != 0) {
  309. fprintf(stderr, "<size> must be 32-bit hex (0x[0-9a-f]+)\n");
  310. exit(OTHER_ERROR);
  311. }
  312. bytes = calloc(1, length);
  313. if (bytes == NULL) {
  314. perror("calloc");
  315. exit(OTHER_ERROR);
  316. }
  317. result = TlclGetRandom(bytes, length, &size);
  318. if (result == 0 && size > 0) {
  319. for (i = 0; i < size; i++) {
  320. printf("%02x", bytes[i]);
  321. }
  322. printf("\n");
  323. }
  324. free(bytes);
  325. return result;
  326. }
  327. static uint32_t HandlerGetPermanentFlags(void) {
  328. TPM_PERMANENT_FLAGS pflags;
  329. uint32_t result = TlclGetPermanentFlags(&pflags);
  330. if (result == 0) {
  331. #define P(name) printf("%s %d\n", #name, pflags.name)
  332. #ifdef TPM2_MODE
  333. P(ownerAuthSet);
  334. P(endorsementAuthSet);
  335. P(lockoutAuthSet);
  336. P(disableClear);
  337. P(inLockout);
  338. P(tpmGeneratedEPS);
  339. #else
  340. P(disable);
  341. P(ownership);
  342. P(deactivated);
  343. P(readPubek);
  344. P(disableOwnerClear);
  345. P(allowMaintenance);
  346. P(physicalPresenceLifetimeLock);
  347. P(physicalPresenceHWEnable);
  348. P(physicalPresenceCMDEnable);
  349. P(CEKPUsed);
  350. P(TPMpost);
  351. P(TPMpostLock);
  352. P(FIPS);
  353. P(Operator);
  354. P(enableRevokeEK);
  355. P(nvLocked);
  356. P(readSRKPub);
  357. P(tpmEstablished);
  358. P(maintenanceDone);
  359. P(disableFullDALogicInfo);
  360. #endif
  361. #undef P
  362. }
  363. return result;
  364. }
  365. static uint32_t HandlerGetSTClearFlags(void) {
  366. TPM_STCLEAR_FLAGS vflags;
  367. uint32_t result = TlclGetSTClearFlags(&vflags);
  368. if (result == 0) {
  369. #define P(name) printf("%s %d\n", #name, vflags.name)
  370. #ifdef TPM2_MODE
  371. P(phEnable);
  372. P(shEnable);
  373. P(ehEnable);
  374. P(phEnableNV);
  375. P(orderly);
  376. #else
  377. P(deactivated);
  378. P(disableForceClear);
  379. P(physicalPresence);
  380. P(physicalPresenceLock);
  381. P(bGlobalLock);
  382. #endif
  383. #undef P
  384. }
  385. return result;
  386. }
  387. static uint32_t HandlerSendRaw(void) {
  388. uint8_t request[4096];
  389. uint8_t response[4096];
  390. uint32_t result;
  391. int size;
  392. int i;
  393. if (nargs == 2) {
  394. fprintf(stderr, "usage: tpmc sendraw <hex byte 0> ... <hex byte N>\n");
  395. exit(OTHER_ERROR);
  396. }
  397. for (i = 0; i < nargs - 2 && i < sizeof(request); i++) {
  398. if (HexStringToUint8(args[2 + i], &request[i]) != 0) {
  399. fprintf(stderr, "bad byte value \"%s\"\n", args[2 + i]);
  400. exit(OTHER_ERROR);
  401. }
  402. }
  403. size = TlclPacketSize(request);
  404. if (size != i) {
  405. fprintf(stderr, "bad request: size field is %d, but packet has %d bytes\n",
  406. size, i);
  407. exit(OTHER_ERROR);
  408. }
  409. bzero(response, sizeof(response));
  410. result = TlclSendReceive(request, response, sizeof(response));
  411. if (result != 0) {
  412. fprintf(stderr, "request failed with code %d\n", result);
  413. }
  414. size = TlclPacketSize(response);
  415. if (size < 10 || size > sizeof(response)) {
  416. fprintf(stderr, "unexpected response size %d\n", size);
  417. exit(OTHER_ERROR);
  418. }
  419. for (i = 0; i < size; i++) {
  420. printf("0x%02x ", response[i]);
  421. if (i == size - 1 || (i + 1) % 8 == 0) {
  422. printf("\n");
  423. }
  424. }
  425. return result;
  426. }
  427. #ifdef TPM2_MODE
  428. static uint32_t HandlerDoNothingForTPM2(void) {
  429. return 0;
  430. }
  431. static uint32_t HandlerNotImplementedForTPM2(void) {
  432. fprintf(stderr, "%s: not implemented for TPM2.0\n", args[1]);
  433. exit(OTHER_ERROR);
  434. }
  435. #endif
  436. /* Table of TPM commands.
  437. */
  438. command_record command_table[] = {
  439. { "tpmversion", "tpmver", "print TPM version: 1.2 or 2.0",
  440. HandlerTpmVersion },
  441. { "getflags", "getf", "read and print the value of selected flags",
  442. HandlerGetFlags },
  443. { "startup", "sta", "issue a Startup command", TlclStartup },
  444. { "selftestfull", "test", "issue a SelfTestFull command", TlclSelfTestFull },
  445. { "continueselftest", "ctest", "issue a ContinueSelfTest command",
  446. TlclContinueSelfTest },
  447. { "assertphysicalpresence", "ppon",
  448. TPM20_DOES_NOTHING("assert Physical Presence",
  449. TlclAssertPhysicalPresence) },
  450. { "physicalpresencecmdenable", "ppcmd",
  451. TPM20_NOT_IMPLEMENTED("turn on software PP",
  452. TlclPhysicalPresenceCMDEnable) },
  453. { "enable", "ena",
  454. TPM20_DOES_NOTHING("enable the TPM" TPM12_NEEDS_PP,
  455. TlclSetEnable) },
  456. { "disable", "dis",
  457. TPM20_NOT_IMPLEMENTED("disable the TPM" TPM12_NEEDS_PP,
  458. TlclClearEnable) },
  459. { "activate", "act",
  460. TPM20_DOES_NOTHING("activate the TPM" TPM12_NEEDS_PP_REBOOT,
  461. HandlerActivate) },
  462. { "deactivate", "deact",
  463. TPM20_NOT_IMPLEMENTED("deactivate the TPM" TPM12_NEEDS_PP_REBOOT,
  464. HandlerDeactivate) },
  465. { "clear", "clr",
  466. "clear the TPM owner" TPM12_NEEDS_PP,
  467. TlclForceClear },
  468. { "setnvlocked", "setnv",
  469. TPM20_NOT_IMPLEMENTED("set the nvLocked flag permanently (IRREVERSIBLE!)",
  470. TlclSetNvLocked) },
  471. { "lockphysicalpresence", "pplock",
  472. TPM_MODE_SELECT("lock (turn off) PP until reboot",
  473. "set rollback protection lock for kernel image until reboot"),
  474. TlclLockPhysicalPresence },
  475. { "setbgloballock", "block",
  476. TPM_MODE_SELECT("set the bGlobalLock until reboot",
  477. "set rollback protection lock for R/W firmware until reboot"),
  478. TlclSetGlobalLock },
  479. { "definespace", "def", "define a space (def <index> <size> <perm>)",
  480. HandlerDefineSpace },
  481. { "write", "write", "write to a space (write <index> [<byte0> <byte1> ...])",
  482. HandlerWrite },
  483. { "read", "read", "read from a space (read <index> <size>)",
  484. HandlerRead },
  485. { "pcrread", "pcr", "read from a PCR (pcrread <index>)",
  486. HandlerPCRRead },
  487. { "pcrextend", "extend", "extend a PCR (extend <index> <extend_hash>)",
  488. HandlerPCRExtend },
  489. { "getownership", "geto", "print state of TPM ownership",
  490. HandlerGetOwnership },
  491. { "getpermissions", "getp", "print space permissions (getp <index>)",
  492. HandlerGetPermissions },
  493. { "getpermanentflags", "getpf", "print all permanent flags",
  494. HandlerGetPermanentFlags },
  495. { "getrandom", "rand", "read bytes from RNG (rand <size>)",
  496. HandlerGetRandom },
  497. { "getstclearflags", "getvf", "print all volatile (ST_CLEAR) flags",
  498. HandlerGetSTClearFlags },
  499. { "resume", "res", "execute TPM_Startup(ST_STATE)", TlclResume },
  500. { "savestate", "save", "execute TPM_SaveState", TlclSaveState },
  501. { "sendraw", "raw", "send a raw request and print raw response",
  502. HandlerSendRaw },
  503. };
  504. static int n_commands = sizeof(command_table) / sizeof(command_table[0]);
  505. int main(int argc, char* argv[]) {
  506. char *progname;
  507. uint32_t result;
  508. progname = strrchr(argv[0], '/');
  509. if (progname)
  510. progname++;
  511. else
  512. progname = argv[0];
  513. if (argc < 2) {
  514. fprintf(stderr, "usage: %s <TPM command> [args]\n or: %s help\n",
  515. progname, progname);
  516. return OTHER_ERROR;
  517. } else {
  518. command_record* c;
  519. const char* cmd = argv[1];
  520. nargs = argc;
  521. args = argv;
  522. if (strcmp(cmd, "help") == 0) {
  523. printf("tpmc mode: TPM%s\n", TPM_MODE_STRING);
  524. printf("%26s %7s %s\n\n", "command", "abbr.", "description");
  525. for (c = command_table; c < command_table + n_commands; c++) {
  526. printf("%26s %7s %s\n", c->name, c->abbr, c->description);
  527. }
  528. return 0;
  529. }
  530. if (!strcmp(cmd, "tpmversion") || !strcmp(cmd, "tpmver")) {
  531. return HandlerTpmVersion();
  532. }
  533. result = TlclLibInit();
  534. if (result) {
  535. fprintf(stderr, "initialization failed with code %d\n", result);
  536. return result > OTHER_ERROR ? OTHER_ERROR : result;
  537. }
  538. for (c = command_table; c < command_table + n_commands; c++) {
  539. if (strcmp(cmd, c->name) == 0 || strcmp(cmd, c->abbr) == 0) {
  540. return ErrorCheck(c->handler(), cmd);
  541. }
  542. }
  543. /* No command matched. */
  544. fprintf(stderr, "%s: unknown command: %s\n", progname, cmd);
  545. return OTHER_ERROR;
  546. }
  547. }