[NET] Fix some 'net help' and 'net user' issues.
[reactos.git] / base / applications / network / net / cmdUser.c
index c9d07c4..0cb5dac 100644 (file)
@@ -1,19 +1,20 @@
 /*
  * COPYRIGHT:       See COPYING in the top level directory
  * PROJECT:         ReactOS net command
- * FILE:
+ * FILE:            base/applications/network/net/cmdUser.c
  * PURPOSE:
  *
  * PROGRAMMERS:     Eric Kohl
+ *                  Curtis Wilson
  */
 
 #include "net.h"
 
+static WCHAR szPasswordChars[] = L"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@#$%_-+:";
 
 static
 int
-CompareInfo(const void *a,
-            const void *b)
+CompareInfo(const void *a, const void *b)
 {
     return _wcsicmp(((PUSER_INFO_0)a)->usri0_name,
                     ((PUSER_INFO_0)b)->usri0_name);
@@ -28,7 +29,7 @@ EnumerateUsers(VOID)
     PSERVER_INFO_100 pServer = NULL;
     DWORD dwRead = 0, dwTotal = 0;
     DWORD i;
-    DWORD_PTR ResumeHandle = 0;
+    DWORD ResumeHandle = 0;
     NET_API_STATUS Status;
 
     Status = NetServerGetInfo(NULL,
@@ -37,37 +38,42 @@ EnumerateUsers(VOID)
     if (Status != NERR_Success)
         return Status;
 
-    PrintToConsole(L"\nUser accounts for \\\\%s\n\n", pServer->sv100_name);
+    ConPuts(StdOut, L"\n");
+    ConResPrintf(StdOut, IDS_USER_ACCOUNTS, pServer->sv100_name);
+    ConPuts(StdOut, L"\n\n");
+    PrintPadding(L'-', 79);
+    ConPuts(StdOut, L"\n");
 
     NetApiBufferFree(pServer);
 
-    PrintToConsole(L"------------------------------------------\n");
-
-    Status = NetUserEnum(NULL,
-                         0,
-                         0,
-                         (LPBYTE*)&pBuffer,
-                         MAX_PREFERRED_LENGTH,
-                         &dwRead,
-                         &dwTotal,
-                         &ResumeHandle);
-    if (Status != NERR_Success)
-        return Status;
-
-    qsort(pBuffer,
-          dwRead,
-          sizeof(PUSER_INFO_0),
-          CompareInfo);
-
-//    printf("dwRead: %lu  dwTotal: %lu\n", dwRead, dwTotal);
-    for (i = 0; i < dwRead; i++)
+    do
     {
-//        printf("%p\n", pBuffer[i].lgrpi0_name);
-         if (pBuffer[i].usri0_name)
-            PrintToConsole(L"%s\n", pBuffer[i].usri0_name);
-    }
+        Status = NetUserEnum(NULL,
+                             0,
+                             0,
+                             (LPBYTE*)&pBuffer,
+                             MAX_PREFERRED_LENGTH,
+                             &dwRead,
+                             &dwTotal,
+                             &ResumeHandle);
+        if ((Status != NERR_Success) && (Status != ERROR_MORE_DATA))
+            return Status;
+
+        qsort(pBuffer,
+              dwRead,
+              sizeof(PUSER_INFO_0),
+              CompareInfo);
+
+        for (i = 0; i < dwRead; i++)
+        {
+            if (pBuffer[i].usri0_name)
+                ConPrintf(StdOut, L"%s\n", pBuffer[i].usri0_name);
+        }
 
-    NetApiBufferFree(pBuffer);
+        NetApiBufferFree(pBuffer);
+        pBuffer = NULL;
+    }
+    while (Status == ERROR_MORE_DATA);
 
     return NERR_Success;
 }
@@ -103,7 +109,7 @@ PrintDateTime(DWORD dwSeconds)
                    TimeBuffer,
                    80);
 
-    PrintToConsole(L"%s %s\n", DateBuffer, TimeBuffer);
+    ConPrintf(StdOut, L"%s %s", DateBuffer, TimeBuffer);
 }
 
 
@@ -136,6 +142,7 @@ DisplayUser(LPWSTR lpUserName)
     DWORD dwGroupRead, dwGroupTotal;
     DWORD dwLastSet;
     DWORD i;
+    INT nPaddedLength = 29;
     NET_API_STATUS Status;
 
     /* Modify the user */
@@ -173,81 +180,118 @@ DisplayUser(LPWSTR lpUserName)
     if (Status != NERR_Success)
         goto done;
 
-    PrintToConsole(L"User name                    %s\n", pUserInfo->usri4_name);
-    PrintToConsole(L"Full name                    %s\n", pUserInfo->usri4_full_name);
-    PrintToConsole(L"Comment                      %s\n", pUserInfo->usri4_comment);
-    PrintToConsole(L"User comment                 %s\n", pUserInfo->usri4_usr_comment);
-    PrintToConsole(L"Country code                 %03ld ()\n", pUserInfo->usri4_country_code);
-    PrintToConsole(L"Account active               %s\n", (pUserInfo->usri4_flags & UF_ACCOUNTDISABLE)? L"No" : ((pUserInfo->usri4_flags & UF_LOCKOUT) ? L"Locked" : L"Yes"));
-    PrintToConsole(L"Account expires              ");
+    PrintPaddedResourceString(IDS_USER_NAME, nPaddedLength);
+    ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_name);
+
+    PrintPaddedResourceString(IDS_USER_FULL_NAME, nPaddedLength);
+    ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_full_name);
+
+    PrintPaddedResourceString(IDS_USER_COMMENT, nPaddedLength);
+    ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_comment);
+
+    PrintPaddedResourceString(IDS_USER_USER_COMMENT, nPaddedLength);
+    ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_usr_comment);
+
+    PrintPaddedResourceString(IDS_USER_COUNTRY_CODE, nPaddedLength);
+    ConPrintf(StdOut, L"%03ld ()\n", pUserInfo->usri4_country_code);
+
+    PrintPaddedResourceString(IDS_USER_ACCOUNT_ACTIVE, nPaddedLength);
+    if (pUserInfo->usri4_flags & UF_ACCOUNTDISABLE)
+        ConResPuts(StdOut, IDS_GENERIC_NO);
+    else if (pUserInfo->usri4_flags & UF_LOCKOUT)
+        ConResPuts(StdOut, IDS_GENERIC_LOCKED);
+    else
+        ConResPuts(StdOut, IDS_GENERIC_YES);
+    ConPuts(StdOut, L"\n");
+
+    PrintPaddedResourceString(IDS_USER_ACCOUNT_EXPIRES, nPaddedLength);
     if (pUserInfo->usri4_acct_expires == TIMEQ_FOREVER)
-        PrintToConsole(L"Never\n");
+        ConResPuts(StdOut, IDS_GENERIC_NEVER);
     else
         PrintDateTime(pUserInfo->usri4_acct_expires);
+    ConPuts(StdOut, L"\n\n");
 
-    PrintToConsole(L"\n");
-
-    PrintToConsole(L"Password last set            ");
+    PrintPaddedResourceString(IDS_USER_PW_LAST_SET, nPaddedLength);
     dwLastSet = GetTimeInSeconds() - pUserInfo->usri4_password_age;
     PrintDateTime(dwLastSet);
+    ConPuts(StdOut, L"\n");
 
-    PrintToConsole(L"Password expires             ");
+    PrintPaddedResourceString(IDS_USER_PW_EXPIRES, nPaddedLength);
     if ((pUserInfo->usri4_flags & UF_DONT_EXPIRE_PASSWD) || pUserModals->usrmod0_max_passwd_age == TIMEQ_FOREVER)
-        PrintToConsole(L"Never\n");
+        ConResPuts(StdOut, IDS_GENERIC_NEVER);
     else
         PrintDateTime(dwLastSet + pUserModals->usrmod0_max_passwd_age);
+    ConPuts(StdOut, L"\n");
 
-    PrintToConsole(L"Password changeable          ");
+    PrintPaddedResourceString(IDS_USER_PW_CHANGEABLE, nPaddedLength);
     PrintDateTime(dwLastSet + pUserModals->usrmod0_min_passwd_age);
+    ConPuts(StdOut, L"\n");
+
+    PrintPaddedResourceString(IDS_USER_PW_REQUIRED, nPaddedLength);
+    ConResPuts(StdOut, (pUserInfo->usri4_flags & UF_PASSWD_NOTREQD) ? IDS_GENERIC_NO : IDS_GENERIC_YES);
+    ConPuts(StdOut, L"\n");
+
+    PrintPaddedResourceString(IDS_USER_CHANGE_PW, nPaddedLength);
+    ConResPuts(StdOut, (pUserInfo->usri4_flags & UF_PASSWD_CANT_CHANGE) ? IDS_GENERIC_NO : IDS_GENERIC_YES);
+    ConPuts(StdOut, L"\n\n");
+
+    PrintPaddedResourceString(IDS_USER_WORKSTATIONS, nPaddedLength);
+    if (pUserInfo->usri4_workstations == NULL || wcslen(pUserInfo->usri4_workstations) == 0)
+        ConResPuts(StdOut, IDS_GENERIC_ALL);
+    else
+        ConPrintf(StdOut, L"%s", pUserInfo->usri4_workstations);
+    ConPuts(StdOut, L"\n");
+
+    PrintPaddedResourceString(IDS_USER_LOGON_SCRIPT, nPaddedLength);
+    ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_script_path);
 
-    PrintToConsole(L"Password required            %s\n", (pUserInfo->usri4_flags & UF_PASSWD_NOTREQD) ? L"No" : L"Yes");
-    PrintToConsole(L"User may change password     %s\n", (pUserInfo->usri4_flags & UF_PASSWD_CANT_CHANGE) ? L"No" : L"Yes");
+    PrintPaddedResourceString(IDS_USER_PROFILE, nPaddedLength);
+    ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_profile);
 
-    PrintToConsole(L"\n");
-    PrintToConsole(L"Workstations allowed         %s\n", (pUserInfo->usri4_workstations == NULL || wcslen(pUserInfo->usri4_workstations) == 0) ? L"All" : pUserInfo->usri4_workstations);
-    PrintToConsole(L"Logon script                 %s\n", pUserInfo->usri4_script_path);
-    PrintToConsole(L"User profile                 %s\n", pUserInfo->usri4_profile);
-    PrintToConsole(L"Home directory               %s\n", pUserInfo->usri4_home_dir);
-    PrintToConsole(L"Last logon                   ");
+    PrintPaddedResourceString(IDS_USER_HOME_DIR, nPaddedLength);
+    ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_home_dir);
+
+    PrintPaddedResourceString(IDS_USER_LAST_LOGON, nPaddedLength);
     if (pUserInfo->usri4_last_logon == 0)
-        PrintToConsole(L"Never\n");
+        ConResPuts(StdOut, IDS_GENERIC_NEVER);
     else
         PrintDateTime(pUserInfo->usri4_last_logon);
-    PrintToConsole(L"\n");
-    PrintToConsole(L"Logon hours allowed          ");
+    ConPuts(StdOut, L"\n\n");
+
+    PrintPaddedResourceString(IDS_USER_LOGON_HOURS, nPaddedLength);
     if (pUserInfo->usri4_logon_hours == NULL)
-        PrintToConsole(L"All\n");
-    PrintToConsole(L"\n");
+        ConResPuts(StdOut, IDS_GENERIC_ALL);
+    ConPuts(StdOut, L"\n\n");
 
-    PrintToConsole(L"\n");
-    PrintToConsole(L"Local group memberships      ");
+    ConPuts(StdOut, L"\n");
+    PrintPaddedResourceString(IDS_USER_LOCAL_GROUPS, nPaddedLength);
     if (dwLocalGroupTotal != 0 && pLocalGroupInfo != NULL)
     {
         for (i = 0; i < dwLocalGroupTotal; i++)
         {
             if (i != 0)
-                PrintToConsole(L"                             ");
-            PrintToConsole(L"*%s\n", pLocalGroupInfo[i].lgrui0_name);
+                PrintPadding(L' ', nPaddedLength);
+            ConPrintf(StdOut, L"*%s\n", pLocalGroupInfo[i].lgrui0_name);
         }
     }
     else
     {
-        PrintToConsole(L"\n");
+        ConPuts(StdOut, L"\n");
     }
 
-    PrintToConsole(L"Global group memberships     ");
+    PrintPaddedResourceString(IDS_USER_GLOBAL_GROUPS, nPaddedLength);
     if (dwGroupTotal != 0 && pGroupInfo != NULL)
     {
         for (i = 0; i < dwGroupTotal; i++)
         {
             if (i != 0)
-                PrintToConsole(L"                             ");
-            PrintToConsole(L"*%s\n", pGroupInfo[i].grui0_name);
+                PrintPadding(L' ', nPaddedLength);
+            ConPrintf(StdOut, L"*%s\n", pGroupInfo[i].grui0_name);
         }
     }
     else
     {
-        PrintToConsole(L"\n");
+        ConPuts(StdOut, L"\n");
     }
 
 done:
@@ -267,6 +311,307 @@ done:
 }
 
 
+static
+VOID
+ReadPassword(
+    LPWSTR *lpPassword,
+    LPBOOL lpAllocated)
+{
+    WCHAR szPassword1[PWLEN + 1];
+    WCHAR szPassword2[PWLEN + 1];
+    LPWSTR ptr;
+
+    *lpAllocated = FALSE;
+
+    while (TRUE)
+    {
+        ConResPuts(StdOut, IDS_USER_ENTER_PASSWORD1);
+        ReadFromConsole(szPassword1, PWLEN + 1, FALSE);
+        ConPuts(StdOut, L"\n");
+
+        ConResPuts(StdOut, IDS_USER_ENTER_PASSWORD2);
+        ReadFromConsole(szPassword2, PWLEN + 1, FALSE);
+        ConPuts(StdOut, L"\n");
+
+        if (wcslen(szPassword1) == wcslen(szPassword2) &&
+            wcscmp(szPassword1, szPassword2) == 0)
+        {
+            ptr = HeapAlloc(GetProcessHeap(),
+                            0,
+                            (wcslen(szPassword1) + 1) * sizeof(WCHAR));
+            if (ptr != NULL)
+            {
+                wcscpy(ptr, szPassword1);
+                *lpPassword = ptr;
+                *lpAllocated = TRUE;
+                return;
+            }
+        }
+        else
+        {
+            ConPuts(StdOut, L"\n");
+            ConResPuts(StdOut, IDS_USER_NO_PASSWORD_MATCH);
+            ConPuts(StdOut, L"\n");
+            *lpPassword = NULL;
+        }
+    }
+}
+
+
+static
+VOID
+GenerateRandomPassword(
+    LPWSTR *lpPassword,
+    LPBOOL lpAllocated)
+{
+    LPWSTR pPassword = NULL;
+    INT nCharsLen, i, nLength = 8;
+
+    srand(GetTickCount());
+
+    pPassword = HeapAlloc(GetProcessHeap(),
+                          HEAP_ZERO_MEMORY,
+                          (nLength + 1) * sizeof(WCHAR));
+    if (pPassword == NULL)
+        return;
+
+    nCharsLen = wcslen(szPasswordChars);
+
+    for (i = 0; i < nLength; i++)
+    {
+        pPassword[i] = szPasswordChars[rand() % nCharsLen];
+    }
+
+    *lpPassword = pPassword;
+    *lpAllocated = TRUE;
+}
+
+
+static
+NET_API_STATUS
+BuildWorkstationsList(
+    _Out_ PWSTR *pWorkstationsList,
+    _In_ PWSTR pRaw)
+{
+    BOOL isLastSep, isSep;
+    INT i, j;
+    WCHAR c;
+    INT nLength = 0;
+    INT nArgs = 0;
+    INT nRawLength;
+    PWSTR pList;
+
+    /* Check for invalid characters in the raw string */
+    if (wcspbrk(pRaw, L"/[]=?\\+:.") != NULL)
+        return 3952;
+
+    /* Count the number of workstations in the list and
+     * the required buffer size */
+    isLastSep = FALSE;
+    isSep = FALSE;
+    nRawLength = wcslen(pRaw);
+    for (i = 0; i < nRawLength; i++)
+    {
+        c = pRaw[i];
+        if (c == L',' || c == L';')
+            isSep = TRUE;
+
+        if (isSep == TRUE)
+        {
+            if ((isLastSep == FALSE) && (i != 0) && (i != nRawLength - 1))
+                nLength++;
+        }
+        else
+        {
+            nLength++;
+
+            if (isLastSep == TRUE || (isLastSep == FALSE && i == 0))
+                nArgs++;
+        }
+
+        isLastSep = isSep;
+        isSep = FALSE;
+    }
+
+    nLength++;
+
+    /* Leave, if there are no workstations in the list */
+    if (nArgs == 0)
+    {
+        pWorkstationsList = NULL;
+        return NERR_Success;
+    }
+
+    /* Fail if there are more than eight workstations in the list */
+    if (nArgs > 8)
+        return 3951;
+
+    /* Allocate the buffer for the clean workstation list */
+    pList = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nLength * sizeof(WCHAR));
+    if (pList == NULL)
+        return ERROR_NOT_ENOUGH_MEMORY;
+
+    /* Build the clean workstation list */
+    isLastSep = FALSE;
+    isSep = FALSE;
+    nRawLength = wcslen(pRaw);
+    for (i = 0, j = 0; i < nRawLength; i++)
+    {
+        c = pRaw[i];
+        if (c == L',' || c == L';')
+            isSep = TRUE;
+
+        if (isSep == TRUE)
+        {
+            if ((isLastSep == FALSE) && (i != 0) && (i != nRawLength - 1))
+            {
+                pList[j] = L',';
+                j++;
+            }
+        }
+        else
+        {
+            pList[j] = c;
+            j++;
+
+            if (isLastSep == TRUE || (isLastSep == FALSE && i == 0))
+                nArgs++;
+        }
+
+        isLastSep = isSep;
+        isSep = FALSE;
+    }
+
+    *pWorkstationsList = pList;
+
+    return NERR_Success;
+}
+
+
+static
+BOOL
+ReadNumber(
+    PWSTR *s,
+    PWORD pwValue)
+{
+    if (!iswdigit(**s))
+        return FALSE;
+
+    while (iswdigit(**s))
+    {
+        *pwValue = *pwValue * 10 + **s - L'0';
+        (*s)++;
+    }
+
+    return TRUE;
+}
+
+
+static
+BOOL
+ReadSeparator(
+    PWSTR *s)
+{
+    if (**s == L'/' || **s == L'.')
+    {
+        (*s)++;
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+static
+BOOL
+ParseDate(
+    PWSTR s,
+    PULONG pSeconds)
+{
+    SYSTEMTIME SystemTime = {0};
+    FILETIME LocalFileTime, FileTime;
+    LARGE_INTEGER Time;
+    INT nDateFormat = 0;
+    PWSTR p = s;
+
+    if (!*s)
+        return FALSE;
+
+    GetLocaleInfoW(LOCALE_USER_DEFAULT,
+                   LOCALE_IDATE,
+                   (PWSTR)&nDateFormat,
+                   sizeof(INT));
+
+    switch (nDateFormat)
+    {
+        case 0: /* mmddyy */
+        default:
+            if (!ReadNumber(&p, &SystemTime.wMonth))
+                return FALSE;
+            if (!ReadSeparator(&p))
+                return FALSE;
+            if (!ReadNumber(&p, &SystemTime.wDay))
+                return FALSE;
+            if (!ReadSeparator(&p))
+                return FALSE;
+            if (!ReadNumber(&p, &SystemTime.wYear))
+                return FALSE;
+            break;
+
+        case 1: /* ddmmyy */
+            if (!ReadNumber(&p, &SystemTime.wDay))
+                return FALSE;
+            if (!ReadSeparator(&p))
+                return FALSE;
+            if (!ReadNumber(&p, &SystemTime.wMonth))
+                return FALSE;
+            if (!ReadSeparator(&p))
+                return FALSE;
+            if (!ReadNumber(&p, &SystemTime.wYear))
+                return FALSE;
+            break;
+
+        case 2: /* yymmdd */
+            if (!ReadNumber(&p, &SystemTime.wYear))
+                return FALSE;
+            if (!ReadSeparator(&p))
+                return FALSE;
+            if (!ReadNumber(&p, &SystemTime.wMonth))
+                return FALSE;
+            if (!ReadSeparator(&p))
+                return FALSE;
+            if (!ReadNumber(&p, &SystemTime.wDay))
+                return FALSE;
+            break;
+    }
+
+    /* if only entered two digits: */
+    /*   assume 2000's if value less than 80 */
+    /*   assume 1900's if value greater or equal 80 */
+    if (SystemTime.wYear <= 99)
+    {
+        if (SystemTime.wYear >= 80)
+            SystemTime.wYear += 1900;
+        else
+            SystemTime.wYear += 2000;
+    }
+
+    if (!SystemTimeToFileTime(&SystemTime, &LocalFileTime))
+        return FALSE;
+
+    if (!LocalFileTimeToFileTime(&LocalFileTime, &FileTime))
+        return FALSE;
+
+    Time.u.LowPart = FileTime.dwLowDateTime;
+    Time.u.HighPart = FileTime.dwHighDateTime;
+
+    if (!RtlTimeToSecondsSince1970(&Time, pSeconds))
+        return FALSE;
+
+    return TRUE;
+}
+
+
 INT
 cmdUser(
     INT argc,
@@ -279,40 +624,30 @@ cmdUser(
 #if 0
     BOOL bDomain = FALSE;
 #endif
+    BOOL bRandomPassword = FALSE;
     LPWSTR lpUserName = NULL;
     LPWSTR lpPassword = NULL;
     PUSER_INFO_4 pUserInfo = NULL;
     USER_INFO_4 UserInfo;
+    LPWSTR pWorkstations = NULL;
     LPWSTR p;
     LPWSTR endptr;
     DWORD value;
+    BOOL bPasswordAllocated = FALSE;
     NET_API_STATUS Status;
 
-    if (argc == 2)
-    {
-        Status = EnumerateUsers();
-        printf("Status: %lu\n", Status);
-        return 0;
-    }
-    else if (argc == 3)
-    {
-        Status = DisplayUser(argv[2]);
-        printf("Status: %lu\n", Status);
-        return 0;
-    }
-
     i = 2;
-    if (argv[i][0] != L'/')
+    if ((i < argc) && (argv[i][0] != L'/'))
     {
         lpUserName = argv[i];
-        printf("User: %S\n", lpUserName);
+//        ConPrintf(StdOut, L"User: %s\n", lpUserName);
         i++;
     }
 
-    if (argv[i][0] != L'/')
+    if ((i < argc) && (argv[i][0] != L'/'))
     {
         lpPassword = argv[i];
-        printf("Password: %S\n", lpPassword);
+//        ConPrintf(StdOut, L"Password: %s\n", lpPassword);
         i++;
     }
 
@@ -320,7 +655,7 @@ cmdUser(
     {
         if (_wcsicmp(argv[j], L"/help") == 0)
         {
-            PrintResourceString(IDS_USER_HELP);
+            PrintNetMessage(MSG_USER_HELP);
             return 0;
         }
         else if (_wcsicmp(argv[j], L"/add") == 0)
@@ -333,11 +668,30 @@ cmdUser(
         }
         else if (_wcsicmp(argv[j], L"/domain") == 0)
         {
-            printf("The /DOMAIN option is not supported yet!\n");
+            ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/DOMAIN");
 #if 0
             bDomain = TRUE;
 #endif
         }
+        else if (_wcsicmp(argv[j], L"/random") == 0)
+        {
+            bRandomPassword = TRUE;
+            GenerateRandomPassword(&lpPassword,
+                                   &bPasswordAllocated);
+        }
+    }
+
+    if (lpUserName == NULL && lpPassword == NULL)
+    {
+        Status = EnumerateUsers();
+        ConPrintf(StdOut, L"Status: %lu\n", Status);
+        return 0;
+    }
+    else if (lpUserName != NULL && lpPassword == NULL)
+    {
+        Status = DisplayUser(lpUserName);
+        ConPrintf(StdOut, L"Status: %lu\n", Status);
+        return 0;
     }
 
     if (bAdd && bDelete)
@@ -346,6 +700,13 @@ cmdUser(
         goto done;
     }
 
+    /* Interactive password input */
+    if (lpPassword != NULL && wcscmp(lpPassword, L"*") == 0)
+    {
+        ReadPassword(&lpPassword,
+                     &bPasswordAllocated);
+    }
+
     if (!bAdd && !bDelete)
     {
         /* Modify the user */
@@ -355,7 +716,7 @@ cmdUser(
                                 (LPBYTE*)&pUserInfo);
         if (Status != NERR_Success)
         {
-            printf("Status: %lu\n", Status);
+            ConPrintf(StdOut, L"Status: %lu\n", Status);
             result = 1;
             goto done;
         }
@@ -368,6 +729,8 @@ cmdUser(
         UserInfo.usri4_name = lpUserName;
         UserInfo.usri4_password = lpPassword;
         UserInfo.usri4_flags = UF_SCRIPT | UF_NORMAL_ACCOUNT;
+        UserInfo.usri4_acct_expires = TIMEQ_FOREVER;
+        UserInfo.usri4_primary_group_id = DOMAIN_GROUP_RID_USERS;
 
         pUserInfo = &UserInfo;
     }
@@ -387,7 +750,7 @@ cmdUser(
             }
             else
             {
-                PrintToConsole(L"You entered an invalid value for the /ACTIVE option.\n");
+                ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/ACTIVE");
                 result = 1;
                 goto done;
             }
@@ -402,7 +765,7 @@ cmdUser(
             value = wcstoul(p, &endptr, 10);
             if (*endptr != 0)
             {
-                PrintToConsole(L"You entered an invalid value for the /COUNTRYCODE option.\n");
+                ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/COUNTRYCODE");
                 result = 1;
                 goto done;
             }
@@ -413,6 +776,20 @@ cmdUser(
         }
         else if (_wcsnicmp(argv[j], L"/expires:", 9) == 0)
         {
+            p = &argv[i][9];
+            if (_wcsicmp(p, L"never") == 0)
+            {
+                pUserInfo->usri4_acct_expires = TIMEQ_FOREVER;
+            }
+            else if (!ParseDate(p, &pUserInfo->usri4_acct_expires))
+            {
+                ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/EXPIRES");
+                result = 1;
+                goto done;
+
+                /* FIXME: Parse the date */
+//                ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/EXPIRES");
+            }
         }
         else if (_wcsnicmp(argv[j], L"/fullname:", 10) == 0)
         {
@@ -435,7 +812,7 @@ cmdUser(
             }
             else
             {
-                PrintToConsole(L"You entered an invalid value for the /PASSWORDCHG option.\n");
+                ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/PASSWORDCHG");
                 result = 1;
                 goto done;
             }
@@ -453,7 +830,7 @@ cmdUser(
             }
             else
             {
-                PrintToConsole(L"You entered an invalid value for the /PASSWORDREQ option.\n");
+                ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/PASSWORDREQ");
                 result = 1;
                 goto done;
             }
@@ -468,6 +845,8 @@ cmdUser(
         }
         else if (_wcsnicmp(argv[j], L"/times:", 7) == 0)
         {
+            /* FIXME */
+            ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/TIMES");
         }
         else if (_wcsnicmp(argv[j], L"/usercomment:", 13) == 0)
         {
@@ -475,6 +854,25 @@ cmdUser(
         }
         else if (_wcsnicmp(argv[j], L"/workstations:", 14) == 0)
         {
+            p = &argv[i][14];
+            if (wcscmp(p, L"*") == 0 || wcscmp(p, L"") == 0)
+            {
+                pUserInfo->usri4_workstations = NULL;
+            }
+            else
+            {
+                Status = BuildWorkstationsList(&pWorkstations, p);
+                if (Status == NERR_Success)
+                {
+                    pUserInfo->usri4_workstations = pWorkstations;
+                }
+                else
+                {
+                    ConPrintf(StdOut, L"Status %lu\n\n", Status);
+                    result = 1;
+                    goto done;
+                }
+            }
         }
     }
 
@@ -486,7 +884,7 @@ cmdUser(
                                 4,
                                 (LPBYTE)pUserInfo,
                                 NULL);
-        printf("Status: %lu\n", Status);
+        ConPrintf(StdOut, L"Status: %lu\n", Status);
     }
     else if (bAdd && !bDelete)
     {
@@ -495,22 +893,38 @@ cmdUser(
                             4,
                             (LPBYTE)pUserInfo,
                             NULL);
-        printf("Status: %lu\n", Status);
+        ConPrintf(StdOut, L"Status: %lu\n", Status);
     }
     else if (!bAdd && bDelete)
     {
         /* Delete the user */
         Status = NetUserDel(NULL,
                             lpUserName);
-        printf("Status: %lu\n", Status);
+        ConPrintf(StdOut, L"Status: %lu\n", Status);
+    }
+
+    if (Status == NERR_Success &&
+        lpPassword != NULL &&
+        bRandomPassword == TRUE)
+    {
+        ConPrintf(StdOut, L"The password for %s is: %s\n", lpUserName, lpPassword);
     }
 
 done:
+    if (pWorkstations != NULL)
+        HeapFree(GetProcessHeap(), 0, pWorkstations);
+
+    if ((bPasswordAllocated == TRUE) && (lpPassword != NULL))
+        HeapFree(GetProcessHeap(), 0, lpPassword);
+
     if (!bAdd && !bDelete && pUserInfo != NULL)
         NetApiBufferFree(pUserInfo);
 
     if (result != 0)
-        PrintResourceString(IDS_USER_SYNTAX);
+    {
+        ConResPuts(StdOut, IDS_GENERIC_SYNTAX);
+        PrintNetMessage(MSG_USER_SYNTAX);
+    }
 
     return result;
 }