Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

744 lines
21 KiB

  1. //+---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 2001.
  5. //
  6. // File: cmdkey: cmdkey.cpp
  7. //
  8. // Contents: Main & command modules
  9. //
  10. // Classes:
  11. //
  12. // Functions:
  13. //
  14. // History: 07-09-01 georgema Created
  15. //
  16. //----------------------------------------------------------------------------
  17. #include <windows.h>
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <wincred.h>
  22. #include <wincrui.h>
  23. #include "command.h"
  24. #include "io.h"
  25. #include "utils.h"
  26. #include "consmsg.h"
  27. #include "switches.h"
  28. #ifdef VERBOSE
  29. WCHAR szdbg[500]; // scratch char buffer for verbose output
  30. #endif
  31. /********************************************************************
  32. GLOBAL VARIABLES, mostly argument parsing results
  33. ********************************************************************/
  34. int returnvalue = 0;
  35. // Switch flag model characters
  36. #define VALIDSWITCHES 9 // A G L D HELP R U P S
  37. // Define an array of character models and constants for the index into the array
  38. // for each
  39. WCHAR rgcS[] = {L"agld?rups"}; // referenced also in command.cpp
  40. #define SWADD 0
  41. #define SWGENERIC 1
  42. #define SWLIST 2
  43. #define SWDELETE 3
  44. #define SWHELP 4
  45. #define SWSESSION 5
  46. #define SWUSER 6
  47. #define SWPASSWORD 7
  48. #define SWCARD 8
  49. // variables for these to avoid repeated function calls into the parser
  50. BOOL fAdd = FALSE;
  51. BOOL fSession = FALSE;
  52. BOOL fCard = FALSE;
  53. BOOL fGeneric = FALSE;
  54. BOOL fDelete = FALSE;
  55. BOOL fList = FALSE;
  56. BOOL fUser = FALSE;
  57. BOOL fNew = FALSE; // sets true for any cred-creating switch (asg)
  58. WCHAR SessionTarget[]={L"*Session"};
  59. // temp buffer used for composing output strings involving a marshalled username
  60. WCHAR szUsername[CRED_MAX_USERNAME_LENGTH + 1]; // 513 wchars
  61. /********************************************************************
  62. Conversion routines from enumerated values to their string equivalents
  63. String values are held in an array of 63 character max strings
  64. ********************************************************************/
  65. #define SBUFSIZE 64
  66. #define TYPECOUNT 5
  67. #define MAPTYPE(x) (x >= TYPECOUNT ? 0 : x)
  68. WCHAR TString[TYPECOUNT][SBUFSIZE + 1];
  69. #define PERSISTCOUNT 4
  70. #define PERTYPE(x) (x>=PERSISTCOUNT ? 0 : x)
  71. WCHAR PString[PERSISTCOUNT][SBUFSIZE + 1];
  72. // Preload some strings from the application resources into arrays to be used by the
  73. // application. These strings describe some enumerated DWORD values.
  74. BOOL
  75. AppInit(void)
  76. {
  77. // Allocate 2K WCHAR buffer to be used for string composition
  78. if (NULL == szOut)
  79. {
  80. szOut = (WCHAR *) malloc((STRINGMAXLEN + 1) * sizeof(WCHAR));
  81. if (NULL == szOut) return FALSE;
  82. }
  83. // Preload string from resources into buffers allocated on the stack as an array
  84. // Total array size is 9 x 65 WCHARs = 1190 bytes
  85. wcsncpy(TString[0],ComposeString(MSG_TYPE0),SBUFSIZE);
  86. TString[0][SBUFSIZE] = 0;
  87. wcsncpy(TString[1],ComposeString(MSG_TYPE1),SBUFSIZE);
  88. TString[1][SBUFSIZE] = 0;
  89. wcsncpy(TString[2],ComposeString(MSG_TYPE2),SBUFSIZE);
  90. TString[2][SBUFSIZE] = 0;
  91. wcsncpy(TString[3],ComposeString(MSG_TYPE3),SBUFSIZE);
  92. TString[3][SBUFSIZE] = 0;
  93. wcsncpy(TString[4],ComposeString(MSG_TYPE4),SBUFSIZE);
  94. TString[4][SBUFSIZE] = 0;
  95. wcsncpy(PString[0],ComposeString(MSG_PERSIST0),SBUFSIZE);
  96. PString[0][SBUFSIZE] = 0;
  97. wcsncpy(PString[1],ComposeString(MSG_PERSIST1),SBUFSIZE);
  98. PString[1][SBUFSIZE] = 0;
  99. wcsncpy(PString[2],ComposeString(MSG_PERSIST2),SBUFSIZE);
  100. PString[2][SBUFSIZE] = 0;
  101. wcsncpy(PString[3],ComposeString(MSG_PERSIST3),SBUFSIZE);
  102. PString[3][SBUFSIZE] = 0;
  103. if (PString[3][0] == 0) return FALSE;
  104. return TRUE;
  105. }
  106. WCHAR
  107. *TypeString(DWORD dwType)
  108. {
  109. return TString[MAPTYPE(dwType)];
  110. }
  111. WCHAR
  112. *PerString(DWORD dwType)
  113. {
  114. return PString[PERTYPE(dwType)];
  115. }
  116. /********************************************************************
  117. Get operating modes
  118. (Code stolen from Credui:: CredUIApiInit()
  119. ********************************************************************/
  120. #define MODEPERSONAL 1
  121. #define MODESAFE 2
  122. #define MODEDC 4
  123. DWORD GetOSMode(void)
  124. {
  125. DWORD dwMode = 0;
  126. // Check for Personal SKU:
  127. OSVERSIONINFOEXW versionInfo;
  128. versionInfo.dwOSVersionInfoSize = sizeof OSVERSIONINFOEXW;
  129. if (GetVersionEx(reinterpret_cast<OSVERSIONINFOW *>(&versionInfo)))
  130. {
  131. if ((versionInfo.wProductType == VER_NT_WORKSTATION) &&
  132. (versionInfo.wSuiteMask & VER_SUITE_PERSONAL))
  133. dwMode |= MODEPERSONAL;
  134. if (versionInfo.wProductType == VER_NT_DOMAIN_CONTROLLER)
  135. dwMode |= MODEDC;
  136. }
  137. // Check for safe mode:
  138. HKEY key;
  139. if (RegOpenKeyEx(
  140. HKEY_LOCAL_MACHINE,
  141. L"SYSTEM\\CurrentControlSet\\Control\\SafeBoot\\Option",
  142. 0,
  143. KEY_READ,
  144. &key) == ERROR_SUCCESS)
  145. {
  146. if (RegQueryValueEx(
  147. key,
  148. L"OptionValue",
  149. NULL,
  150. NULL,
  151. NULL,
  152. NULL) == ERROR_SUCCESS)
  153. {
  154. dwMode |= MODESAFE;
  155. }
  156. RegCloseKey(key);
  157. }
  158. #ifdef VERBOSE
  159. if (dwMode & MODEPERSONAL) OutputDebugString(L"CMDKEY: OS MODE - PERSONAL\n");
  160. if (dwMode & MODEDC) OutputDebugString(L"CMDKEY: OS MODE - DOMAIN CONTROLLER\n");
  161. if (dwMode & MODESAFE) OutputDebugString(L"CMDKEY: OS MODE - SAFE BOOT\n");
  162. #endif
  163. return dwMode;
  164. }
  165. /********************************************************************
  166. Get allowable persistence value for the current logon session
  167. taken from keymgr: krdlg.cpp
  168. ********************************************************************/
  169. DWORD GetPersistenceOptions(DWORD dwPType) {
  170. BOOL bResult;
  171. DWORD i[CRED_TYPE_MAXIMUM];
  172. DWORD dwCount = CRED_TYPE_MAXIMUM;
  173. bResult = CredGetSessionTypes(dwCount,&i[0]);
  174. if (!bResult) {
  175. return CRED_PERSIST_NONE;
  176. }
  177. return i[dwPType];
  178. }
  179. /********************************************************************
  180. COMMAND HANDLERS
  181. ********************************************************************/
  182. DWORD DoAdd(INT argc, char **argv)
  183. {
  184. // Add a credential to the keyring.
  185. // Note that the *Session credential is created in this routine, as well, though it uses
  186. // a different command line switch than /a.
  187. // For the *Session credential, the persistence is changed to session persistence, and
  188. // the targetname is always "*Session". When the targetname appears on the command
  189. // line UI, however, it is replaced by "<dialup session>" to mimic the behavior of keymgr.
  190. // these buffers needed if we have to prompt
  191. WCHAR szUser[CREDUI_MAX_USERNAME_LENGTH + 1]; // 513 wchars
  192. WCHAR szPass[CREDUI_MAX_PASSWORD_LENGTH + 1]; // 257 wchars
  193. CREDENTIAL stCred;
  194. DWORD Persist;
  195. WCHAR *pT = NULL; // targetname pointer
  196. BOOL fP = FALSE; // password switch present
  197. BOOL fS = FALSE; // smartcard if /C
  198. WCHAR *pU = CLPtr(SWUSER);
  199. WCHAR *pP = CLPtr(SWPASSWORD);
  200. DWORD dwErr = NO_ERROR;
  201. BOOL IsPersonal;
  202. szUser[0] = 0;
  203. szPass[0] = 0;
  204. IsPersonal = (GetOSMode() & MODEPERSONAL);
  205. // Get default persistence value
  206. if (IsPersonal)
  207. {
  208. if (fSession)
  209. Persist = CRED_PERSIST_SESSION;
  210. else
  211. Persist = GetPersistenceOptions(CRED_TYPE_GENERIC);
  212. }
  213. else
  214. {
  215. Persist = GetPersistenceOptions(CRED_TYPE_DOMAIN_PASSWORD);
  216. }
  217. // Error if no saves allowed and not on personal
  218. // In personal, error of add operation unless session flag present
  219. if (
  220. (CRED_PERSIST_NONE== Persist) ||
  221. (IsPersonal & (!(fSession || fGeneric)))
  222. )
  223. {
  224. PrintString(MSG_CANNOTADD);
  225. StompCommandLine(argc,argv);
  226. return -1; // special value -1 will suppress default error message generation
  227. }
  228. if (fGeneric)
  229. {
  230. pT = CLPtr(SWGENERIC);
  231. }
  232. else
  233. {
  234. pT = CLPtr(SWADD); // default targetname - may be overridden by generic or session switches
  235. }
  236. #ifdef VERBOSE
  237. // Some logging for debug purposes
  238. if (pT)
  239. swprintf(szdbg,L"CMDKEY: Target = %s\n",pT);
  240. else
  241. swprintf(szdbg,L"CMDKEY: Target = NULL\n");
  242. OutputDebugString(szdbg);
  243. if (pU)
  244. swprintf(szdbg,L"CMDKEY: User = %s\n",pU);
  245. else
  246. swprintf(szdbg,L"CMDKEY: User is NULL\n");
  247. OutputDebugString(szdbg);
  248. #endif
  249. // Original username - may be modified by prompting
  250. if (pU)
  251. {
  252. wcsncpy(szUser,pU,CREDUI_MAX_USERNAME_LENGTH);
  253. szUser[CREDUI_MAX_USERNAME_LENGTH] = 0;
  254. }
  255. // Override target name for prompting purposes
  256. ZeroMemory((void *)&stCred, sizeof(CREDENTIAL));
  257. // Prompting block - enter if password or smartcard switch on the commandline
  258. if (CLFlag(SWPASSWORD) || fCard)
  259. {
  260. if ((pP) && (!fCard))
  261. {
  262. stCred.CredentialBlobSize = wcslen(pP) * sizeof(WCHAR);
  263. stCred.CredentialBlob = (BYTE *)pP;
  264. }
  265. else
  266. {
  267. // prompt using credui command line mode
  268. BOOL fSave = TRUE;
  269. DWORD retval = 0;
  270. if (!fCard)
  271. {
  272. retval = CredUICmdLinePromptForCredentials(
  273. pT,
  274. NULL,
  275. 0,
  276. szUser,
  277. CREDUI_MAX_USERNAME_LENGTH,
  278. szPass,
  279. CREDUI_MAX_PASSWORD_LENGTH,
  280. &fSave,
  281. CREDUI_FLAGS_DO_NOT_PERSIST |
  282. CREDUI_FLAGS_EXCLUDE_CERTIFICATES |
  283. CREDUI_FLAGS_GENERIC_CREDENTIALS);
  284. }
  285. else
  286. {
  287. retval = CredUICmdLinePromptForCredentials(
  288. pT,
  289. NULL,
  290. 0,
  291. szUser,
  292. CREDUI_MAX_USERNAME_LENGTH,
  293. szPass,
  294. CREDUI_MAX_PASSWORD_LENGTH,
  295. &fSave,
  296. CREDUI_FLAGS_DO_NOT_PERSIST |
  297. CREDUI_FLAGS_REQUIRE_SMARTCARD
  298. );
  299. }
  300. if (0 != retval)
  301. {
  302. #if DBG
  303. OutputDebugString(L"CMDKEY: CredUI prompt failed\n");
  304. #endif
  305. dwErr = GetLastError();
  306. // dont need to stomp the cmdline, since the psw wasnt on it
  307. return dwErr;
  308. }
  309. else
  310. {
  311. stCred.CredentialBlobSize = wcslen(szPass) * sizeof(WCHAR);
  312. stCred.CredentialBlob = (BYTE *)szPass;
  313. }
  314. }
  315. }
  316. stCred.Persist = Persist;
  317. stCred.TargetName = pT;
  318. stCred.UserName = szUser;
  319. stCred.Type = CRED_TYPE_DOMAIN_PASSWORD;
  320. // Override type and or persistence as necessary
  321. // Override the targetname only in the case of a *Session cred
  322. // Note that generic takes precedence over smartcard
  323. if (fCard)
  324. {
  325. stCred.Type = CRED_TYPE_DOMAIN_CERTIFICATE;
  326. }
  327. if (fGeneric)
  328. {
  329. stCred.Type = CRED_TYPE_GENERIC;
  330. }
  331. if (!CredWrite((PCREDENTIAL)&stCred,0))
  332. {
  333. dwErr = GetLastError();
  334. }
  335. else PrintString(MSG_ADDOK);
  336. StompCommandLine(argc,argv);
  337. SecureZeroMemory(szPass,sizeof(szPass));
  338. return dwErr;
  339. }
  340. // List the creds currently on the keyring. Unlike keymgr, show generic creds as well, though
  341. // they cannot be created with cmdkey in this version.
  342. // NOTE: Generic creds will be created using the /g switch in lieu of /a, just as *Session creds
  343. // are created by /s in lieu of /a.
  344. DWORD DoList(void)
  345. {
  346. PCREDENTIALW *pstC;
  347. DWORD dwCount = 0;
  348. WCHAR sz[500];
  349. WCHAR *pL = CLPtr(SWLIST);
  350. if (pL)
  351. {
  352. szArg[0] = pL;
  353. PrintString(MSG_LISTFOR);
  354. }
  355. else PrintString(MSG_LIST);
  356. if (CredEnumerateW(pL,0,&dwCount,&pstC))
  357. {
  358. for (DWORD i=0;i<dwCount;i++)
  359. {
  360. //Print target: targetname
  361. // type: type string
  362. // username: username from cred
  363. // blank line
  364. PrintString(MSG_TARGETPREAMBLE);
  365. if (!_wcsicmp(pstC[i]->TargetName,SessionTarget))
  366. {
  367. //swprintf(sz,L"Dialup Session Credential\n");
  368. PrintString(MSG_DIALUP);
  369. PutStdout(L"\n");
  370. }
  371. else
  372. {
  373. PutStdout(pstC[i]->TargetName);
  374. PutStdout(L"\n");
  375. }
  376. szArg[0] = TypeString(pstC[i]->Type);
  377. PrintString(MSG_LISTTYPE);
  378. // if the username is NULL, don't show it. This will happen with incomplete RAS
  379. // creds that have not been filled in by Kerberos yet.
  380. if ((pstC[i]->UserName != NULL) &&
  381. (wcslen(pstC[i]->UserName) != 0))
  382. {
  383. // UnMarshallUserName will do so only if it is marshalled. Otherwise will leave it alone
  384. szArg[0] = UnMarshallUserName(pstC[i]->UserName);
  385. PrintString(MSG_LISTNAME);
  386. }
  387. PutStdout(PerString(pstC[i]->Persist));
  388. }
  389. if (pstC) CredFree(pstC);
  390. }
  391. if (0 == dwCount)
  392. {
  393. PrintString(MSG_LISTNONE);
  394. }
  395. return NO_ERROR;
  396. }
  397. // Delete a cred named using the /d switch, or if the /s switch is used, the *Session cred.
  398. DWORD DoDelete(void)
  399. {
  400. BOOL fOK = FALSE;
  401. BOOL fSuccess = FALSE;
  402. DWORD dwErr = -1;
  403. DWORD dw2;
  404. WCHAR *pD = CLPtr(SWDELETE);
  405. // get args from cmd line
  406. // /d:targetname
  407. #ifdef VERBOSE
  408. if (pD)
  409. swprintf(szdbg,L"CMDKEY: Target = %s\n",pD);
  410. else
  411. swprintf(szdbg,L"CMDKEY: Target = NULL\n");
  412. OutputDebugString(szdbg);
  413. #endif
  414. if (fSession)
  415. {
  416. pD = SessionTarget;
  417. }
  418. // remove all creds with this target name
  419. // Any successful delete is success. If no success...
  420. // Failure returned is the last failure found by any attempt, excluding parameter faults
  421. fOK = CredDelete(pD,CRED_TYPE_DOMAIN_PASSWORD,0);
  422. if (!fOK)
  423. {
  424. dw2 = GetLastError();
  425. if ((dwErr != NO_ERROR) && (dw2 != ERROR_INVALID_PARAMETER)) dwErr = dw2;
  426. }
  427. else dwErr = NO_ERROR;
  428. fOK = CredDelete(pD,CRED_TYPE_DOMAIN_VISIBLE_PASSWORD,0);
  429. if (!fOK)
  430. {
  431. dw2 = GetLastError();
  432. if ((dwErr != NO_ERROR) && (dw2 != ERROR_INVALID_PARAMETER)) dwErr = dw2;
  433. }
  434. else dwErr = NO_ERROR;
  435. fOK = CredDelete(pD,CRED_TYPE_GENERIC,0);
  436. if (!fOK)
  437. {
  438. dw2 = GetLastError();
  439. if ((dwErr != NO_ERROR) && (dw2 != ERROR_INVALID_PARAMETER)) dwErr = dw2;
  440. }
  441. else dwErr = NO_ERROR;
  442. fOK = CredDelete(pD,CRED_TYPE_DOMAIN_CERTIFICATE,0);
  443. if (!fOK)
  444. {
  445. dw2 = GetLastError();
  446. if ((dwErr != NO_ERROR) && (dw2 != ERROR_INVALID_PARAMETER)) dwErr = dw2;
  447. }
  448. else dwErr = NO_ERROR;
  449. if (dwErr == NO_ERROR)
  450. {
  451. PrintString(MSG_DELETEOK);
  452. }
  453. return dwErr;
  454. }
  455. /********************************************************************
  456. Command dispatcher and error handling
  457. ********************************************************************/
  458. // Perform the switched command, and display the error returned by GetLastError() if the error
  459. // is both nonzero and not -1.
  460. void
  461. DoCmdKey(INT argc,char **argv)
  462. {
  463. DWORD dwErr = 0;
  464. if (fAdd)
  465. {
  466. dwErr = DoAdd(argc,argv);
  467. }
  468. else if (fDelete)
  469. {
  470. dwErr = DoDelete();
  471. }
  472. else if (fList)
  473. {
  474. dwErr = DoList();
  475. }
  476. // Common residual error handler
  477. if (NO_ERROR != dwErr)
  478. {
  479. returnvalue = 1;
  480. if (dwErr != -1)
  481. {
  482. void *pv = NULL;
  483. PrintString(MSG_PREAMBLE);
  484. if (0 != FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
  485. 0,dwErr,0,(LPTSTR)&pv,500,NULL))
  486. {
  487. PutStdout((WCHAR *)pv);
  488. LocalFree(pv);
  489. }
  490. }
  491. }
  492. }
  493. // show usage string
  494. void
  495. Usage(BOOL fBad)
  496. {
  497. if (fBad) PrintString(MSG_BADCOMMAND);
  498. else PrintString(MSG_CREATES);
  499. PrintString(MSG_USAGE);
  500. return;
  501. }
  502. /********************************************************************
  503. Entry point - argument parsing, validation and call to dispatcher
  504. ********************************************************************/
  505. int __cdecl
  506. main(int argc, char *argv[], char *envp[])
  507. {
  508. INT iArgs = 0; // arg count
  509. BOOL fError = FALSE; // command line error detected
  510. // load needed strings - fail if failed
  511. if (!AppInit())
  512. {
  513. // catastrophic exit
  514. if (szOut) free(szOut);
  515. return 1;
  516. }
  517. //CLInit allocates memory - you must exit via CLUnInit once this call succeeds
  518. if (!CLInit(VALIDSWITCHES,rgcS)) return 1;
  519. CLSetMaxPrincipalSwitch(SWDELETE);
  520. // Parse the command line - fail on duplicated switch
  521. if (!CLParse())
  522. {
  523. PrintString(MSG_DUPLICATE);
  524. returnvalue = 1;
  525. goto bail;
  526. }
  527. // Two ways to get help - no args at all
  528. if (1 == CLTokens())
  529. {
  530. Usage(0);
  531. returnvalue = 1;
  532. goto bail;
  533. }
  534. // Explicit call for help via /?
  535. if (CLFlag(SWHELP))
  536. {
  537. Usage(0);
  538. returnvalue = 1;
  539. goto bail;
  540. }
  541. // Detect extraneous switches - bail if found
  542. #ifdef PICKY
  543. if (CLExtra())
  544. {
  545. Usage(1);
  546. returnvalue = 1;
  547. goto bail;
  548. }
  549. #endif
  550. // Set variables
  551. fAdd = CLFlag(SWADD);
  552. fCard = CLFlag(SWCARD);
  553. fDelete = CLFlag(SWDELETE);
  554. fGeneric = CLFlag(SWGENERIC);
  555. fList = CLFlag(SWLIST);
  556. fSession = CLFlag(SWSESSION);
  557. fUser = CLFlag(SWUSER);
  558. // Command line has been parsed - now look for interactions and missing necessities
  559. // You have to be doing something - test for no principal command
  560. if (CLGetPrincipalSwitch() < 0)
  561. {
  562. // firstcommand value will still be -1 - handled by default below
  563. fError = TRUE;
  564. }
  565. // Weed out illegal combinations, missing arguments to switches where required
  566. if (!fError && fAdd)
  567. {
  568. // need a name arg for this one in its native form
  569. if (!CLPtr(SWADD)) fError = TRUE;
  570. // no contradictory switches
  571. if (fDelete || fList || fGeneric || fSession ) fError = TRUE;
  572. }
  573. if (!fError && fDelete)
  574. {
  575. //need a name arg unless the session switch is on the line
  576. if ((!fSession) &&(!CLPtr(SWDELETE))) fError = TRUE;
  577. //disallow target arg with session switch - success is ambiguous
  578. if ((fSession) && (CLPtr(SWDELETE))) fError = TRUE;
  579. // no contradictory switches
  580. if (fAdd || fList || fGeneric || fUser ) fError = TRUE;
  581. }
  582. if (!fError && fList)
  583. {
  584. // no contradictory switches
  585. if (fDelete || fAdd || fGeneric ||fUser || fSession) fError = TRUE;
  586. }
  587. // The generic flag replaces the add flag, using a different cred type.
  588. // Do not allow it with the Add flag, and insist on a command argument
  589. if (!fError && fGeneric)
  590. {
  591. if (!CLPtr(SWGENERIC)) fError = TRUE;
  592. // no contradictory switches
  593. if (fAdd) fError = TRUE;
  594. // generic cred is an add operation
  595. if (!fError) fAdd = TRUE;
  596. }
  597. // Display help for what we think is the operation the user attempted
  598. // First announce that parameters were bad, then show help
  599. if (fError)
  600. {
  601. PrintString(MSG_BADCOMMAND);
  602. switch(CLGetPrincipalSwitch())
  603. {
  604. case SWADD:
  605. PrintString(MSG_USAGEA);
  606. break;
  607. case SWGENERIC:
  608. PrintString(MSG_USAGEG);
  609. break;
  610. case SWLIST:
  611. PrintString(MSG_USAGEL);
  612. break;
  613. case SWDELETE:
  614. PrintString(MSG_USAGED);
  615. break;
  616. default:
  617. PrintString(MSG_USAGE);
  618. break;
  619. }
  620. goto bail;
  621. }
  622. // Must specify a user for any add operation
  623. // If the smartcard switch is supplied, the username may be absent
  624. if (fAdd)
  625. {
  626. if ((!CLPtr(SWUSER)) && (!fCard))
  627. {
  628. PrintString(MSG_NOUSER);
  629. returnvalue = 1;
  630. goto bail;
  631. }
  632. }
  633. DoCmdKey(argc,argv); // Execute the command action
  634. bail:
  635. CLUnInit();
  636. if (NULL != szOut) free(szOut);
  637. return returnvalue;
  638. }