[NET] Implement a parser for the '/times' option of the 'user' command.
[reactos.git] / base / applications / network / net / cmdUser.c
index 7267a55..919a919 100644 (file)
 
 #include "net.h"
 
+#define SECONDS_PER_DAY (60 * 60 * 24)
+#define SECONDS_PER_HOUR (60 * 60)
+#define HOURS_PER_DAY 24
+#define DAYS_PER_WEEK 7
+
+typedef struct _COUNTY_TABLE
+{
+    DWORD dwCountryCode;
+    DWORD dwMessageId;
+} COUNTRY_TABLE, *PCOUNTRY_TABLE;
+
+
+static WCHAR szPasswordChars[] = L"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@#$%_-+:";
+static COUNTRY_TABLE CountryTable[] =
+{ {  0, 5080},   // System Default
+  {  1, 5081},   // United States
+  {  2, 5082},   // Canada (French)
+  {  3, 5083},   // Latin America
+  { 31, 5084},   // Netherlands
+  { 32, 5085},   // Belgium
+  { 33, 5086},   // France
+  { 34, 5090},   // Spain
+  { 39, 5087},   // Italy
+  { 41, 5088},   // Switzerland
+  { 44, 5089},   // United Kingdom
+  { 45, 5091},   // Denmark
+  { 46, 5092},   // Sweden
+  { 47, 5093},   // Norway
+  { 49, 5094},   // Germany
+  { 61, 5095},   // Australia
+  { 81, 5096},   // Japan
+  { 82, 5097},   // Korea
+  { 86, 5098},   // China (PRC)
+  { 88, 5099},   // Taiwan
+  { 99, 5100},   // Asia
+  {351, 5101},   // Portugal
+  {358, 5102},   // Finland
+  {785, 5103},   // Arabic
+  {972, 5104} }; // Hebrew
+
+//static PWSTR DaysArray[] = {L"So", L"Mo", L"Di", L"Mi", L"Do", L"Fr", L"Sa"};
+static PWSTR DaysArray[] = {L"Sun", L"Mon", L"Tue", L"Wed", L"Thu", L"Fri", L"Sat"};
 
 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);
@@ -29,7 +70,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,
@@ -39,8 +80,8 @@ EnumerateUsers(VOID)
         return Status;
 
     ConPuts(StdOut, L"\n");
-    ConResPrintf(StdOut, IDS_USER_ACCOUNTS, pServer->sv100_name);
-    ConPuts(StdOut, L"\n\n");
+    PrintMessageStringV(4410, pServer->sv100_name);
+    ConPuts(StdOut, L"\n");
     PrintPadding(L'-', 79);
     ConPuts(StdOut, L"\n");
 
@@ -100,19 +141,44 @@ PrintDateTime(DWORD dwSeconds)
                    &SystemTime,
                    NULL,
                    DateBuffer,
-                   80);
+                   ARRAYSIZE(DateBuffer));
 
     GetTimeFormatW(LOCALE_USER_DEFAULT,
                    TIME_NOSECONDS,
                    &SystemTime,
                    NULL,
                    TimeBuffer,
-                   80);
+                   ARRAYSIZE(TimeBuffer));
 
     ConPrintf(StdOut, L"%s %s", DateBuffer, TimeBuffer);
 }
 
 
+static
+VOID
+PrintLocalTime(DWORD dwSeconds)
+{
+    LARGE_INTEGER Time;
+    FILETIME FileTime;
+    SYSTEMTIME SystemTime;
+    WCHAR TimeBuffer[80];
+
+    RtlSecondsSince1970ToTime(dwSeconds, &Time);
+    FileTime.dwLowDateTime = Time.u.LowPart;
+    FileTime.dwHighDateTime = Time.u.HighPart;
+    FileTimeToSystemTime(&FileTime, &SystemTime);
+
+    GetTimeFormatW(LOCALE_USER_DEFAULT,
+                   TIME_NOSECONDS,
+                   &SystemTime,
+                   NULL,
+                   TimeBuffer,
+                   ARRAYSIZE(TimeBuffer));
+
+    ConPrintf(StdOut, L"%s", TimeBuffer);
+}
+
+
 static
 DWORD
 GetTimeInSeconds(VOID)
@@ -130,6 +196,147 @@ GetTimeInSeconds(VOID)
 }
 
 
+static
+BOOL
+GetCountryFromCountryCode(
+    _In_ DWORD dwCountryCode,
+    _In_ DWORD dwCountryLength,
+    _Out_ PWSTR szCountryBuffer)
+{
+    DWORD i;
+
+    for (i = 0; i < ARRAYSIZE(CountryTable); i++)
+    {
+        if (CountryTable[i].dwCountryCode == dwCountryCode)
+        {
+            if (szCountryBuffer != NULL && dwCountryLength > 0)
+            {
+                FormatMessageW(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
+                               hModuleNetMsg,
+                               CountryTable[i].dwMessageId,
+                               LANG_USER_DEFAULT,
+                               szCountryBuffer,
+                               dwCountryLength,
+                               NULL);
+            }
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static
+BOOL
+GetBitValue(
+    PBYTE pBitmap,
+    DWORD dwBitNumber)
+{
+    DWORD dwIndex = dwBitNumber / 8;
+    BYTE Mask = 1 << (dwBitNumber & 7);
+
+    return ((pBitmap[dwIndex] & Mask) != 0);
+}
+
+
+static
+VOID
+SetBitValue(
+    PBYTE pBitmap,
+    DWORD dwBitNumber)
+{
+    DWORD dwIndex = dwBitNumber / 8;
+    BYTE Mask = 1 << (dwBitNumber & 7);
+
+    pBitmap[dwIndex] |= Mask;
+}
+
+
+static
+VOID
+PrintLogonHours(
+    DWORD dwUnitsPerWeek,
+    PBYTE pLogonHours,
+    INT nPaddedLength)
+{
+    DWORD dwUnitsPerDay, dwBitNumber, dwSecondsPerUnit;
+    DWORD dwStartTime, dwEndTime, dwStartDay, dwEndDay;
+    BOOL bBitValue, bFirst = TRUE;
+
+    if ((dwUnitsPerWeek == 0) ||
+        ((dwUnitsPerWeek %7) != 0))
+        return;
+
+    dwUnitsPerDay = dwUnitsPerWeek / 7;
+
+    if (((dwUnitsPerDay % 24) != 0) ||
+        ((dwUnitsPerDay / 24) > 6))
+        return;
+
+    dwSecondsPerUnit = (SECONDS_PER_DAY) / dwUnitsPerDay;
+
+    for (dwBitNumber = 0; dwBitNumber < dwUnitsPerWeek; dwBitNumber++)
+    {
+        bBitValue = GetBitValue(pLogonHours, dwBitNumber);
+        if (bBitValue)
+        {
+            dwStartTime = dwSecondsPerUnit * dwBitNumber;
+
+            while (bBitValue != 0 && dwBitNumber < dwUnitsPerWeek)
+            {
+                dwBitNumber++;
+                if (dwBitNumber < dwUnitsPerWeek)
+                    bBitValue = GetBitValue(pLogonHours, dwBitNumber);
+            }
+
+            dwEndTime = dwSecondsPerUnit * dwBitNumber;
+
+            if (!bFirst)
+                PrintPadding(L' ', nPaddedLength);
+
+            if (dwStartTime == 0 && dwEndTime == (SECONDS_PER_DAY * 7))
+            {
+                PrintMessageString(4302);
+                ConPuts(StdOut, L"\n");
+            }
+            else
+            {
+                dwStartDay = dwStartTime / SECONDS_PER_DAY;
+                dwEndDay = (dwEndTime / SECONDS_PER_DAY) % 7;
+
+                PrintMessageString(4307 + dwStartDay);
+                ConPuts(StdOut, L" ");
+
+                // FIXME: Check if this is a converion from GMT to local timezone
+                PrintLocalTime((dwStartTime % SECONDS_PER_DAY) + SECONDS_PER_HOUR);
+
+                ConPrintf(StdOut, L" - ");
+                if (dwStartDay != dwEndDay)
+                {
+                    PrintMessageString(4307 + dwEndDay);
+                    ConPuts(StdOut, L" ");
+                }
+
+                // FIXME: Check if this is a converion from GMT to local timezone
+                PrintLocalTime((dwEndTime % SECONDS_PER_DAY) + SECONDS_PER_HOUR);
+                ConPuts(StdOut, L"\n");
+            }
+
+            bFirst = FALSE;
+        }
+    }
+
+    if (bFirst)
+    {
+        /* No logon hours */
+        PrintMessageString(4434);
+        ConPuts(StdOut, L"\n");
+    }
+}
+
+
 static
 NET_API_STATUS
 DisplayUser(LPWSTR lpUserName)
@@ -142,7 +349,8 @@ DisplayUser(LPWSTR lpUserName)
     DWORD dwGroupRead, dwGroupTotal;
     DWORD dwLastSet;
     DWORD i;
-    INT nPaddedLength = 29;
+    WCHAR szCountry[40];
+    INT nPaddedLength = 36;
     NET_API_STATUS Status;
 
     /* Modify the user */
@@ -180,91 +388,101 @@ DisplayUser(LPWSTR lpUserName)
     if (Status != NERR_Success)
         goto done;
 
-    PrintPaddedResourceString(IDS_USER_NAME, nPaddedLength);
+    PrintPaddedMessageString(4411, nPaddedLength);
     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_name);
 
-    PrintPaddedResourceString(IDS_USER_FULL_NAME, nPaddedLength);
+    PrintPaddedMessageString(4412, nPaddedLength);
     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_full_name);
 
-    PrintPaddedResourceString(IDS_USER_COMMENT, nPaddedLength);
+    PrintPaddedMessageString(4413, nPaddedLength);
     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_comment);
 
-    PrintPaddedResourceString(IDS_USER_USER_COMMENT, nPaddedLength);
+    PrintPaddedMessageString(4414, 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);
+    PrintPaddedMessageString(4416, nPaddedLength);
+    GetCountryFromCountryCode(pUserInfo->usri4_country_code,
+                              ARRAYSIZE(szCountry), szCountry);
+    ConPrintf(StdOut, L"%03ld (%s)\n", pUserInfo->usri4_country_code, szCountry);
 
-    PrintPaddedResourceString(IDS_USER_ACCOUNT_ACTIVE, nPaddedLength);
+    PrintPaddedMessageString(4419, nPaddedLength);
     if (pUserInfo->usri4_flags & UF_ACCOUNTDISABLE)
-        ConResPuts(StdOut, IDS_GENERIC_NO);
+        PrintMessageString(4301);
     else if (pUserInfo->usri4_flags & UF_LOCKOUT)
-        ConResPuts(StdOut, IDS_GENERIC_LOCKED);
+        PrintMessageString(4440);
     else
-        ConResPuts(StdOut, IDS_GENERIC_YES);
+        PrintMessageString(4300);
     ConPuts(StdOut, L"\n");
 
-    PrintPaddedResourceString(IDS_USER_ACCOUNT_EXPIRES, nPaddedLength);
+    PrintPaddedMessageString(4420, nPaddedLength);
     if (pUserInfo->usri4_acct_expires == TIMEQ_FOREVER)
-        ConResPuts(StdOut, IDS_GENERIC_NEVER);
+        PrintMessageString(4305);
     else
         PrintDateTime(pUserInfo->usri4_acct_expires);
     ConPuts(StdOut, L"\n\n");
 
-    PrintPaddedResourceString(IDS_USER_PW_LAST_SET, nPaddedLength);
+    PrintPaddedMessageString(4421, nPaddedLength);
     dwLastSet = GetTimeInSeconds() - pUserInfo->usri4_password_age;
     PrintDateTime(dwLastSet);
     ConPuts(StdOut, L"\n");
 
-    PrintPaddedResourceString(IDS_USER_PW_EXPIRES, nPaddedLength);
+    PrintPaddedMessageString(4422, nPaddedLength);
     if ((pUserInfo->usri4_flags & UF_DONT_EXPIRE_PASSWD) || pUserModals->usrmod0_max_passwd_age == TIMEQ_FOREVER)
-        ConResPuts(StdOut, IDS_GENERIC_NEVER);
+        PrintMessageString(4305);
     else
         PrintDateTime(dwLastSet + pUserModals->usrmod0_max_passwd_age);
     ConPuts(StdOut, L"\n");
 
-    PrintPaddedResourceString(IDS_USER_PW_CHANGEABLE, nPaddedLength);
+    PrintPaddedMessageString(4423, 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);
+    PrintPaddedMessageString(4437, nPaddedLength);
+    PrintMessageString((pUserInfo->usri4_flags & UF_PASSWD_NOTREQD) ? 4301 : 4300);
     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);
+    PrintPaddedMessageString(4438, nPaddedLength);
+    PrintMessageString((pUserInfo->usri4_flags & UF_PASSWD_CANT_CHANGE) ? 4301 : 4300);
     ConPuts(StdOut, L"\n\n");
 
-    PrintPaddedResourceString(IDS_USER_WORKSTATIONS, nPaddedLength);
+    PrintPaddedMessageString(4424, nPaddedLength);
     if (pUserInfo->usri4_workstations == NULL || wcslen(pUserInfo->usri4_workstations) == 0)
-        ConResPuts(StdOut, IDS_GENERIC_ALL);
+        PrintMessageString(4302);
     else
         ConPrintf(StdOut, L"%s", pUserInfo->usri4_workstations);
     ConPuts(StdOut, L"\n");
 
-    PrintPaddedResourceString(IDS_USER_LOGON_SCRIPT, nPaddedLength);
+    PrintPaddedMessageString(4429, nPaddedLength);
     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_script_path);
 
-    PrintPaddedResourceString(IDS_USER_PROFILE, nPaddedLength);
+    PrintPaddedMessageString(4439, nPaddedLength);
     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_profile);
 
-    PrintPaddedResourceString(IDS_USER_HOME_DIR, nPaddedLength);
+    PrintPaddedMessageString(4436, nPaddedLength);
     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_home_dir);
 
-    PrintPaddedResourceString(IDS_USER_LAST_LOGON, nPaddedLength);
+    PrintPaddedMessageString(4430, nPaddedLength);
     if (pUserInfo->usri4_last_logon == 0)
-        ConResPuts(StdOut, IDS_GENERIC_NEVER);
+        PrintMessageString(4305);
     else
         PrintDateTime(pUserInfo->usri4_last_logon);
     ConPuts(StdOut, L"\n\n");
 
-    PrintPaddedResourceString(IDS_USER_LOGON_HOURS, nPaddedLength);
+    PrintPaddedMessageString(4432, nPaddedLength);
     if (pUserInfo->usri4_logon_hours == NULL)
-        ConResPuts(StdOut, IDS_GENERIC_ALL);
-    ConPuts(StdOut, L"\n\n");
+    {
+        PrintMessageString(4302);
+        ConPuts(StdOut, L"\n");
+    }
+    else
+    {
+        PrintLogonHours(pUserInfo->usri4_units_per_week,
+                        pUserInfo->usri4_logon_hours,
+                        nPaddedLength);
+    }
 
     ConPuts(StdOut, L"\n");
-    PrintPaddedResourceString(IDS_USER_LOCAL_GROUPS, nPaddedLength);
+    PrintPaddedMessageString(4427, nPaddedLength);
     if (dwLocalGroupTotal != 0 && pLocalGroupInfo != NULL)
     {
         for (i = 0; i < dwLocalGroupTotal; i++)
@@ -279,7 +497,7 @@ DisplayUser(LPWSTR lpUserName)
         ConPuts(StdOut, L"\n");
     }
 
-    PrintPaddedResourceString(IDS_USER_GLOBAL_GROUPS, nPaddedLength);
+    PrintPaddedMessageString(4431, nPaddedLength);
     if (dwGroupTotal != 0 && pGroupInfo != NULL)
     {
         for (i = 0; i < dwGroupTotal; i++)
@@ -325,11 +543,11 @@ ReadPassword(
 
     while (TRUE)
     {
-        ConResPuts(StdOut, IDS_USER_ENTER_PASSWORD1);
+        PrintMessageString(4358);
         ReadFromConsole(szPassword1, PWLEN + 1, FALSE);
         ConPuts(StdOut, L"\n");
 
-        ConResPuts(StdOut, IDS_USER_ENTER_PASSWORD2);
+        PrintMessageString(4361);
         ReadFromConsole(szPassword2, PWLEN + 1, FALSE);
         ConPuts(StdOut, L"\n");
 
@@ -350,14 +568,537 @@ ReadPassword(
         else
         {
             ConPuts(StdOut, L"\n");
-            ConResPuts(StdOut, IDS_USER_NO_PASSWORD_MATCH);
-            ConPuts(StdOut, L"\n");
+            PrintMessageString(3728);
             *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;
+}
+
+
+static
+BOOL
+ParseHour(
+    PWSTR pszString,
+    PDWORD pdwHour)
+{
+    PWCHAR pChar;
+    DWORD dwHour = 0;
+
+    if (!iswdigit(pszString[0]))
+        return FALSE;
+
+    pChar = pszString;
+    while (iswdigit(*pChar))
+    {
+        dwHour = dwHour * 10 + *pChar - L'0';
+        pChar++;
+    }
+
+    if (dwHour > 24)
+        return FALSE;
+
+    if (dwHour == 24)
+        dwHour = 0;
+
+    if ((*pChar != UNICODE_NULL) &&
+        (dwHour >= 1) &&
+        (dwHour <= 12))
+    {
+        if ((_wcsicmp(pChar, L"am") == 0) ||
+            (_wcsicmp(pChar, L"a.m.") == 0))
+        {
+            if (dwHour == 12)
+                dwHour = 0;
+        }
+        else if ((_wcsicmp(pChar, L"pm") == 0) ||
+                 (_wcsicmp(pChar, L"p.m.") == 0))
+        {
+            if (dwHour != 12)
+                dwHour += 12;
+        }
+        else
+        {
+            return FALSE;
+        }
+    }
+
+    *pdwHour = dwHour;
+
+    return TRUE;
+}
+
+
+static
+BOOL
+ParseDay(
+    PWSTR pszString,
+    PDWORD pdwDay)
+{
+    DWORD i;
+
+    for (i = 0; i < ARRAYSIZE(DaysArray); i++)
+    {
+        if (_wcsicmp(pszString, DaysArray[i]) == 0)
+        {
+            *pdwDay = i;
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static
+DWORD
+ParseLogonHours(
+    PWSTR pszParams,
+    PBYTE *ppLogonBitmap,
+    PDWORD pdwUnitsPerWeek)
+{
+    PBYTE pLogonBitmap = NULL;
+    DWORD dwError = ERROR_SUCCESS;
+    WCHAR szBuffer[32];
+    PWSTR ptr1, ptr2;
+    WCHAR prevSep, nextSep;
+    DWORD dwStartHour, dwEndHour, dwStartDay, dwEndDay, i, j;
+    BYTE DayBitmap;
+    BYTE HourBitmap[6];
+
+    pLogonBitmap = HeapAlloc(GetProcessHeap(),
+                             HEAP_ZERO_MEMORY,
+                             UNITS_PER_WEEK / 8);
+    if (pLogonBitmap == NULL)
+        return ERROR_OUTOFMEMORY;
+
+    if (*pszParams == UNICODE_NULL)
+    {
+        goto done;
+    }
+
+    if (wcsicmp(pszParams, L"all") == 0)
+    {
+        FillMemory(pLogonBitmap, UNITS_PER_WEEK / 8, 0xFF);
+        goto done;
+    }
+
+    ZeroMemory(&DayBitmap, sizeof(DayBitmap));
+    ZeroMemory(HourBitmap, sizeof(HourBitmap));
+
+    ZeroMemory(szBuffer, sizeof(szBuffer));
+    ptr1 = pszParams;
+    ptr2 = szBuffer;
+    prevSep = UNICODE_NULL;
+    nextSep = UNICODE_NULL;
+    for (;;)
+    {
+        if (*ptr1 != L'-' && *ptr1 != L',' && *ptr1 != L';' && *ptr1 != UNICODE_NULL)
+        {
+            *ptr2 = *ptr1;
+            ptr2++;
+        }
+        else
+        {
+            prevSep = nextSep;
+            nextSep = *ptr1;
+
+//            printf("Token: '%S'\n", szBuffer);
+            if (*ptr1 != UNICODE_NULL)
+                printf("Separator: '%C'\n", *ptr1);
+
+            if (prevSep != L'-')
+            {
+                // Set first value
+                if (iswdigit(szBuffer[0]))
+                {
+                    // parse hour
+                    if (!ParseHour(szBuffer, &dwStartHour))
+                    {
+                        dwError = 3769;
+                        break;
+                    }
+
+                    // FIXME: Check if this is a converion from local timezone to GMT
+                    if (dwStartHour > 0)
+                        dwStartHour--;
+                    else
+                        dwStartHour = UNITS_PER_WEEK - 1;
+
+                    SetBitValue(HourBitmap, dwStartHour);
+                }
+                else
+                {
+                    // parse day
+                    if (!ParseDay(szBuffer, &dwStartDay))
+                    {
+                        dwError = 3768;
+                        break;
+                    }
+
+                    SetBitValue(&DayBitmap, dwStartDay);
+                }
+            }
+            else
+            {
+                // Set second value
+                if (iswdigit(szBuffer[0]))
+                {
+                    // parse hour
+                    if (!ParseHour(szBuffer, &dwEndHour))
+                    {
+                        dwError = 3769;
+                        break;
+                    }
+
+                    if (dwEndHour < dwStartHour)
+                        dwEndHour += HOURS_PER_DAY;
+                    else if (dwEndHour == dwStartHour)
+                        dwEndHour = dwStartHour + HOURS_PER_DAY;
+
+                    // FIXME: Check if this is a converion from local timezone to GMT
+                    if (dwEndHour > 0)
+                        dwEndHour--;
+                    else
+                        dwEndHour = UNITS_PER_WEEK - 1;
+
+                    for (i = dwStartHour; i < dwEndHour; i++)
+                        SetBitValue(HourBitmap, i);
+                }
+                else
+                {
+                    // parse day
+                    if (!ParseDay(szBuffer, &dwEndDay))
+                    {
+                        dwError = 3768;
+                        break;
+                    }
+
+                    if (dwEndDay <= dwStartDay)
+                        dwEndDay += DAYS_PER_WEEK;
+
+                    for (i = dwStartDay; i <= dwEndDay; i++)
+                        SetBitValue(&DayBitmap, i % DAYS_PER_WEEK);
+                }
+            }
+
+            if (*ptr1 == L';' || *ptr1 == UNICODE_NULL)
+            {
+                // Process the data
+//                printf("DayBitmap: %02x  HourBitmap: %02x%02x%02x%02x%02x%02x\n",
+//                       DayBitmap, HourBitmap[5], HourBitmap[4], HourBitmap[3], HourBitmap[2], HourBitmap[1], HourBitmap[0]);
+
+                for (i = 0; i < DAYS_PER_WEEK; i++)
+                {
+                    if (GetBitValue(&DayBitmap, i))
+                    {
+                        for (j = 0; j < 48; j++)
+                        {
+                            if (GetBitValue(HourBitmap, j))
+                                SetBitValue(pLogonBitmap, ((i * HOURS_PER_DAY) + j) % UNITS_PER_WEEK);
+                        }
+                    }
+                }
+
+                // Reset the Bitmaps
+                ZeroMemory(&DayBitmap, sizeof(DayBitmap));
+                ZeroMemory(HourBitmap, sizeof(HourBitmap));
+            }
+
+            if (*ptr1 == UNICODE_NULL)
+            {
+//                printf("Done\n");
+                break;
+            }
+
+            ZeroMemory(szBuffer, sizeof(szBuffer));
+            ptr2 = szBuffer;
+        }
+
+        ptr1++;
+    }
+
+#if 0
+    printf("LogonBitmap:\n");
+    for (i = 0; i < DAYS_PER_WEEK; i++)
+    {
+        j = i * 3;
+        printf("%lu: %02x%02x%02x\n", i, pLogonHours[j + 2], pLogonHours[j + 1], pLogonHours[j + 0]);
+    }
+    printf("\n");
+#endif
+
+done:
+    if (dwError == ERROR_SUCCESS)
+    {
+        *ppLogonBitmap = pLogonBitmap;
+        *pdwUnitsPerWeek = UNITS_PER_WEEK;
+    }
+    else
+    {
+        if (pLogonBitmap != NULL)
+            HeapFree(GetProcessHeap(), 0, pLogonBitmap);
+        *ppLogonBitmap = NULL;
+        *pdwUnitsPerWeek = 0;
+    }
+
+    return dwError;
+}
+
+
 INT
 cmdUser(
     INT argc,
@@ -370,38 +1111,29 @@ 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;
+    PBYTE pLogonHours = NULL;
+    DWORD dwUnitsPerWeek;
     NET_API_STATUS Status;
 
-    if (argc == 2)
-    {
-        Status = EnumerateUsers();
-        ConPrintf(StdOut, L"Status: %lu\n", Status);
-        return 0;
-    }
-    else if (argc == 3)
-    {
-        Status = DisplayUser(argv[2]);
-        ConPrintf(StdOut, L"Status: %lu\n", Status);
-        return 0;
-    }
-
     i = 2;
-    if (argv[i][0] != L'/')
+    if ((i < argc) && (argv[i][0] != L'/'))
     {
         lpUserName = argv[i];
 //        ConPrintf(StdOut, L"User: %s\n", lpUserName);
         i++;
     }
 
-    if (argv[i][0] != L'/')
+    if ((i < argc) && (argv[i][0] != L'/'))
     {
         lpPassword = argv[i];
 //        ConPrintf(StdOut, L"Password: %s\n", lpPassword);
@@ -412,7 +1144,7 @@ cmdUser(
     {
         if (_wcsicmp(argv[j], L"/help") == 0)
         {
-            ConResPuts(StdOut, IDS_USER_HELP);
+            PrintNetMessage(MSG_USER_HELP);
             return 0;
         }
         else if (_wcsicmp(argv[j], L"/add") == 0)
@@ -425,11 +1157,30 @@ cmdUser(
         }
         else if (_wcsicmp(argv[j], L"/domain") == 0)
         {
-            ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/DOMAIN");
+            ConPuts(StdErr, L"The /DOMAIN option is not supported yet.\n");
 #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 && argc == 3)
+    {
+        Status = DisplayUser(lpUserName);
+        ConPrintf(StdOut, L"Status: %lu\n", Status);
+        return 0;
     }
 
     if (bAdd && bDelete)
@@ -467,6 +1218,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;
     }
@@ -486,7 +1239,7 @@ cmdUser(
             }
             else
             {
-                ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/ACTIVE");
+                PrintMessageStringV(3952, L"/ACTIVE");
                 result = 1;
                 goto done;
             }
@@ -501,14 +1254,14 @@ cmdUser(
             value = wcstoul(p, &endptr, 10);
             if (*endptr != 0)
             {
-                ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/COUNTRYCODE");
+                PrintMessageStringV(3952, L"/COUNTRYCODE");
                 result = 1;
                 goto done;
             }
 
-            /* FIXME: verify the country code */
-
-            pUserInfo->usri4_country_code = value;
+            /* Verify the country code */
+            if (GetCountryFromCountryCode(value, 0, NULL))
+                pUserInfo->usri4_country_code = value;
         }
         else if (_wcsnicmp(argv[j], L"/expires:", 9) == 0)
         {
@@ -517,10 +1270,11 @@ cmdUser(
             {
                 pUserInfo->usri4_acct_expires = TIMEQ_FOREVER;
             }
-            else
+            else if (!ParseDate(p, &pUserInfo->usri4_acct_expires))
             {
-                /* FIXME: Parse the date */
-                ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/EXPIRES");
+                PrintMessageStringV(3952, L"/EXPIRES");
+                result = 1;
+                goto done;
             }
         }
         else if (_wcsnicmp(argv[j], L"/fullname:", 10) == 0)
@@ -544,7 +1298,7 @@ cmdUser(
             }
             else
             {
-                ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/PASSWORDCHG");
+                PrintMessageStringV(3952, L"/PASSWORDCHG");
                 result = 1;
                 goto done;
             }
@@ -562,7 +1316,7 @@ cmdUser(
             }
             else
             {
-                ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/PASSWORDREQ");
+                PrintMessageStringV(3952, L"/PASSWORDREQ");
                 result = 1;
                 goto done;
             }
@@ -577,8 +1331,19 @@ cmdUser(
         }
         else if (_wcsnicmp(argv[j], L"/times:", 7) == 0)
         {
-            /* FIXME */
-            ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/TIMES");
+            Status = ParseLogonHours(&argv[j][7],
+                                     &pLogonHours,
+                                     &dwUnitsPerWeek);
+            if (Status == ERROR_SUCCESS)
+            {
+                pUserInfo->usri4_logon_hours = pLogonHours;
+                pUserInfo->usri4_units_per_week = dwUnitsPerWeek;
+            }
+            else
+            {
+                PrintMessageString(Status);
+                goto done;
+            }
         }
         else if (_wcsnicmp(argv[j], L"/usercomment:", 13) == 0)
         {
@@ -586,8 +1351,25 @@ cmdUser(
         }
         else if (_wcsnicmp(argv[j], L"/workstations:", 14) == 0)
         {
-            /* FIXME */
-            ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/WORKSTATIONS");
+            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;
+                }
+            }
         }
     }
 
@@ -618,15 +1400,32 @@ cmdUser(
         ConPrintf(StdOut, L"Status: %lu\n", Status);
     }
 
+    if (Status == NERR_Success &&
+        lpPassword != NULL &&
+        bRandomPassword == TRUE)
+    {
+        PrintMessageStringV(3968, lpUserName, lpPassword);
+    }
+
 done:
-    if ((bPasswordAllocated != FALSE) && (lpPassword != NULL))
+    if (pLogonHours != NULL)
+        HeapFree(GetProcessHeap(), 0, pLogonHours);
+
+    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)
-        ConResPuts(StdOut, IDS_USER_SYNTAX);
+    {
+        PrintMessageString(4381);
+        ConPuts(StdOut, L"\n");
+        PrintNetMessage(MSG_USER_SYNTAX);
+    }
 
     return result;
 }