1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS net command
4  * FILE:            base/applications/network/net/cmdUser.c
5  * PURPOSE:
6  *
7  * PROGRAMMERS:     Eric Kohl
8  *                  Curtis Wilson
9  */
10 
11 #include "net.h"
12 
13 static WCHAR szPasswordChars[] = L"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@#$%_-+:";
14 
15 static
16 int
17 CompareInfo(const void *a, const void *b)
18 {
19     return _wcsicmp(((PUSER_INFO_0)a)->usri0_name,
20                     ((PUSER_INFO_0)b)->usri0_name);
21 }
22 
23 
24 static
25 NET_API_STATUS
26 EnumerateUsers(VOID)
27 {
28     PUSER_INFO_0 pBuffer = NULL;
29     PSERVER_INFO_100 pServer = NULL;
30     DWORD dwRead = 0, dwTotal = 0;
31     DWORD i;
32     DWORD ResumeHandle = 0;
33     NET_API_STATUS Status;
34 
35     Status = NetServerGetInfo(NULL,
36                               100,
37                               (LPBYTE*)&pServer);
38     if (Status != NERR_Success)
39         return Status;
40 
41     ConPuts(StdOut, L"\n");
42     ConResPrintf(StdOut, IDS_USER_ACCOUNTS, pServer->sv100_name);
43     ConPuts(StdOut, L"\n\n");
44     PrintPadding(L'-', 79);
45     ConPuts(StdOut, L"\n");
46 
47     NetApiBufferFree(pServer);
48 
49     do
50     {
51         Status = NetUserEnum(NULL,
52                              0,
53                              0,
54                              (LPBYTE*)&pBuffer,
55                              MAX_PREFERRED_LENGTH,
56                              &dwRead,
57                              &dwTotal,
58                              &ResumeHandle);
59         if ((Status != NERR_Success) && (Status != ERROR_MORE_DATA))
60             return Status;
61 
62         qsort(pBuffer,
63               dwRead,
64               sizeof(PUSER_INFO_0),
65               CompareInfo);
66 
67         for (i = 0; i < dwRead; i++)
68         {
69             if (pBuffer[i].usri0_name)
70                 ConPrintf(StdOut, L"%s\n", pBuffer[i].usri0_name);
71         }
72 
73         NetApiBufferFree(pBuffer);
74         pBuffer = NULL;
75     }
76     while (Status == ERROR_MORE_DATA);
77 
78     return NERR_Success;
79 }
80 
81 
82 static
83 VOID
84 PrintDateTime(DWORD dwSeconds)
85 {
86     LARGE_INTEGER Time;
87     FILETIME FileTime;
88     SYSTEMTIME SystemTime;
89     WCHAR DateBuffer[80];
90     WCHAR TimeBuffer[80];
91 
92     RtlSecondsSince1970ToTime(dwSeconds, &Time);
93     FileTime.dwLowDateTime = Time.u.LowPart;
94     FileTime.dwHighDateTime = Time.u.HighPart;
95     FileTimeToLocalFileTime(&FileTime, &FileTime);
96     FileTimeToSystemTime(&FileTime, &SystemTime);
97 
98     GetDateFormatW(LOCALE_USER_DEFAULT,
99                    DATE_SHORTDATE,
100                    &SystemTime,
101                    NULL,
102                    DateBuffer,
103                    80);
104 
105     GetTimeFormatW(LOCALE_USER_DEFAULT,
106                    TIME_NOSECONDS,
107                    &SystemTime,
108                    NULL,
109                    TimeBuffer,
110                    80);
111 
112     ConPrintf(StdOut, L"%s %s", DateBuffer, TimeBuffer);
113 }
114 
115 
116 static
117 DWORD
118 GetTimeInSeconds(VOID)
119 {
120     LARGE_INTEGER Time;
121     FILETIME FileTime;
122     DWORD dwSeconds;
123 
124     GetSystemTimeAsFileTime(&FileTime);
125     Time.u.LowPart = FileTime.dwLowDateTime;
126     Time.u.HighPart = FileTime.dwHighDateTime;
127     RtlTimeToSecondsSince1970(&Time, &dwSeconds);
128 
129     return dwSeconds;
130 }
131 
132 
133 static
134 NET_API_STATUS
135 DisplayUser(LPWSTR lpUserName)
136 {
137     PUSER_MODALS_INFO_0 pUserModals = NULL;
138     PUSER_INFO_4 pUserInfo = NULL;
139     PLOCALGROUP_USERS_INFO_0 pLocalGroupInfo = NULL;
140     PGROUP_USERS_INFO_0 pGroupInfo = NULL;
141     DWORD dwLocalGroupRead, dwLocalGroupTotal;
142     DWORD dwGroupRead, dwGroupTotal;
143     DWORD dwLastSet;
144     DWORD i;
145     INT nPaddedLength = 29;
146     NET_API_STATUS Status;
147 
148     /* Modify the user */
149     Status = NetUserGetInfo(NULL,
150                             lpUserName,
151                             4,
152                             (LPBYTE*)&pUserInfo);
153     if (Status != NERR_Success)
154         return Status;
155 
156     Status = NetUserModalsGet(NULL,
157                               0,
158                               (LPBYTE*)&pUserModals);
159     if (Status != NERR_Success)
160         goto done;
161 
162     Status = NetUserGetLocalGroups(NULL,
163                                    lpUserName,
164                                    0,
165                                    0,
166                                    (LPBYTE*)&pLocalGroupInfo,
167                                    MAX_PREFERRED_LENGTH,
168                                    &dwLocalGroupRead,
169                                    &dwLocalGroupTotal);
170     if (Status != NERR_Success)
171         goto done;
172 
173     Status = NetUserGetGroups(NULL,
174                               lpUserName,
175                               0,
176                               (LPBYTE*)&pGroupInfo,
177                               MAX_PREFERRED_LENGTH,
178                               &dwGroupRead,
179                               &dwGroupTotal);
180     if (Status != NERR_Success)
181         goto done;
182 
183     PrintPaddedResourceString(IDS_USER_NAME, nPaddedLength);
184     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_name);
185 
186     PrintPaddedResourceString(IDS_USER_FULL_NAME, nPaddedLength);
187     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_full_name);
188 
189     PrintPaddedResourceString(IDS_USER_COMMENT, nPaddedLength);
190     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_comment);
191 
192     PrintPaddedResourceString(IDS_USER_USER_COMMENT, nPaddedLength);
193     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_usr_comment);
194 
195     PrintPaddedResourceString(IDS_USER_COUNTRY_CODE, nPaddedLength);
196     ConPrintf(StdOut, L"%03ld ()\n", pUserInfo->usri4_country_code);
197 
198     PrintPaddedResourceString(IDS_USER_ACCOUNT_ACTIVE, nPaddedLength);
199     if (pUserInfo->usri4_flags & UF_ACCOUNTDISABLE)
200         ConResPuts(StdOut, IDS_GENERIC_NO);
201     else if (pUserInfo->usri4_flags & UF_LOCKOUT)
202         ConResPuts(StdOut, IDS_GENERIC_LOCKED);
203     else
204         ConResPuts(StdOut, IDS_GENERIC_YES);
205     ConPuts(StdOut, L"\n");
206 
207     PrintPaddedResourceString(IDS_USER_ACCOUNT_EXPIRES, nPaddedLength);
208     if (pUserInfo->usri4_acct_expires == TIMEQ_FOREVER)
209         ConResPuts(StdOut, IDS_GENERIC_NEVER);
210     else
211         PrintDateTime(pUserInfo->usri4_acct_expires);
212     ConPuts(StdOut, L"\n\n");
213 
214     PrintPaddedResourceString(IDS_USER_PW_LAST_SET, nPaddedLength);
215     dwLastSet = GetTimeInSeconds() - pUserInfo->usri4_password_age;
216     PrintDateTime(dwLastSet);
217     ConPuts(StdOut, L"\n");
218 
219     PrintPaddedResourceString(IDS_USER_PW_EXPIRES, nPaddedLength);
220     if ((pUserInfo->usri4_flags & UF_DONT_EXPIRE_PASSWD) || pUserModals->usrmod0_max_passwd_age == TIMEQ_FOREVER)
221         ConResPuts(StdOut, IDS_GENERIC_NEVER);
222     else
223         PrintDateTime(dwLastSet + pUserModals->usrmod0_max_passwd_age);
224     ConPuts(StdOut, L"\n");
225 
226     PrintPaddedResourceString(IDS_USER_PW_CHANGEABLE, nPaddedLength);
227     PrintDateTime(dwLastSet + pUserModals->usrmod0_min_passwd_age);
228     ConPuts(StdOut, L"\n");
229 
230     PrintPaddedResourceString(IDS_USER_PW_REQUIRED, nPaddedLength);
231     ConResPuts(StdOut, (pUserInfo->usri4_flags & UF_PASSWD_NOTREQD) ? IDS_GENERIC_NO : IDS_GENERIC_YES);
232     ConPuts(StdOut, L"\n");
233 
234     PrintPaddedResourceString(IDS_USER_CHANGE_PW, nPaddedLength);
235     ConResPuts(StdOut, (pUserInfo->usri4_flags & UF_PASSWD_CANT_CHANGE) ? IDS_GENERIC_NO : IDS_GENERIC_YES);
236     ConPuts(StdOut, L"\n\n");
237 
238     PrintPaddedResourceString(IDS_USER_WORKSTATIONS, nPaddedLength);
239     if (pUserInfo->usri4_workstations == NULL || wcslen(pUserInfo->usri4_workstations) == 0)
240         ConResPuts(StdOut, IDS_GENERIC_ALL);
241     else
242         ConPrintf(StdOut, L"%s", pUserInfo->usri4_workstations);
243     ConPuts(StdOut, L"\n");
244 
245     PrintPaddedResourceString(IDS_USER_LOGON_SCRIPT, nPaddedLength);
246     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_script_path);
247 
248     PrintPaddedResourceString(IDS_USER_PROFILE, nPaddedLength);
249     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_profile);
250 
251     PrintPaddedResourceString(IDS_USER_HOME_DIR, nPaddedLength);
252     ConPrintf(StdOut, L"%s\n", pUserInfo->usri4_home_dir);
253 
254     PrintPaddedResourceString(IDS_USER_LAST_LOGON, nPaddedLength);
255     if (pUserInfo->usri4_last_logon == 0)
256         ConResPuts(StdOut, IDS_GENERIC_NEVER);
257     else
258         PrintDateTime(pUserInfo->usri4_last_logon);
259     ConPuts(StdOut, L"\n\n");
260 
261     PrintPaddedResourceString(IDS_USER_LOGON_HOURS, nPaddedLength);
262     if (pUserInfo->usri4_logon_hours == NULL)
263         ConResPuts(StdOut, IDS_GENERIC_ALL);
264     ConPuts(StdOut, L"\n\n");
265 
266     ConPuts(StdOut, L"\n");
267     PrintPaddedResourceString(IDS_USER_LOCAL_GROUPS, nPaddedLength);
268     if (dwLocalGroupTotal != 0 && pLocalGroupInfo != NULL)
269     {
270         for (i = 0; i < dwLocalGroupTotal; i++)
271         {
272             if (i != 0)
273                 PrintPadding(L' ', nPaddedLength);
274             ConPrintf(StdOut, L"*%s\n", pLocalGroupInfo[i].lgrui0_name);
275         }
276     }
277     else
278     {
279         ConPuts(StdOut, L"\n");
280     }
281 
282     PrintPaddedResourceString(IDS_USER_GLOBAL_GROUPS, nPaddedLength);
283     if (dwGroupTotal != 0 && pGroupInfo != NULL)
284     {
285         for (i = 0; i < dwGroupTotal; i++)
286         {
287             if (i != 0)
288                 PrintPadding(L' ', nPaddedLength);
289             ConPrintf(StdOut, L"*%s\n", pGroupInfo[i].grui0_name);
290         }
291     }
292     else
293     {
294         ConPuts(StdOut, L"\n");
295     }
296 
297 done:
298     if (pGroupInfo != NULL)
299         NetApiBufferFree(pGroupInfo);
300 
301     if (pLocalGroupInfo != NULL)
302         NetApiBufferFree(pLocalGroupInfo);
303 
304     if (pUserModals != NULL)
305         NetApiBufferFree(pUserModals);
306 
307     if (pUserInfo != NULL)
308         NetApiBufferFree(pUserInfo);
309 
310     return NERR_Success;
311 }
312 
313 
314 static
315 VOID
316 ReadPassword(
317     LPWSTR *lpPassword,
318     LPBOOL lpAllocated)
319 {
320     WCHAR szPassword1[PWLEN + 1];
321     WCHAR szPassword2[PWLEN + 1];
322     LPWSTR ptr;
323 
324     *lpAllocated = FALSE;
325 
326     while (TRUE)
327     {
328         ConResPuts(StdOut, IDS_USER_ENTER_PASSWORD1);
329         ReadFromConsole(szPassword1, PWLEN + 1, FALSE);
330         ConPuts(StdOut, L"\n");
331 
332         ConResPuts(StdOut, IDS_USER_ENTER_PASSWORD2);
333         ReadFromConsole(szPassword2, PWLEN + 1, FALSE);
334         ConPuts(StdOut, L"\n");
335 
336         if (wcslen(szPassword1) == wcslen(szPassword2) &&
337             wcscmp(szPassword1, szPassword2) == 0)
338         {
339             ptr = HeapAlloc(GetProcessHeap(),
340                             0,
341                             (wcslen(szPassword1) + 1) * sizeof(WCHAR));
342             if (ptr != NULL)
343             {
344                 wcscpy(ptr, szPassword1);
345                 *lpPassword = ptr;
346                 *lpAllocated = TRUE;
347                 return;
348             }
349         }
350         else
351         {
352             ConPuts(StdOut, L"\n");
353             ConResPuts(StdOut, IDS_USER_NO_PASSWORD_MATCH);
354             ConPuts(StdOut, L"\n");
355             *lpPassword = NULL;
356         }
357     }
358 }
359 
360 
361 static
362 VOID
363 GenerateRandomPassword(
364     LPWSTR *lpPassword,
365     LPBOOL lpAllocated)
366 {
367     LPWSTR pPassword = NULL;
368     INT nCharsLen, i, nLength = 8;
369 
370     srand(GetTickCount());
371 
372     pPassword = HeapAlloc(GetProcessHeap(),
373                           HEAP_ZERO_MEMORY,
374                           (nLength + 1) * sizeof(WCHAR));
375     if (pPassword == NULL)
376         return;
377 
378     nCharsLen = wcslen(szPasswordChars);
379 
380     for (i = 0; i < nLength; i++)
381     {
382         pPassword[i] = szPasswordChars[rand() % nCharsLen];
383     }
384 
385     *lpPassword = pPassword;
386     *lpAllocated = TRUE;
387 }
388 
389 
390 static
391 NET_API_STATUS
392 BuildWorkstationsList(
393     _Out_ PWSTR *pWorkstationsList,
394     _In_ PWSTR pRaw)
395 {
396     BOOL isLastSep, isSep;
397     INT i, j;
398     WCHAR c;
399     INT nLength = 0;
400     INT nArgs = 0;
401     INT nRawLength;
402     PWSTR pList;
403 
404     /* Check for invalid characters in the raw string */
405     if (wcspbrk(pRaw, L"/[]=?\\+:.") != NULL)
406         return 3952;
407 
408     /* Count the number of workstations in the list and
409      * the required buffer size */
410     isLastSep = FALSE;
411     isSep = FALSE;
412     nRawLength = wcslen(pRaw);
413     for (i = 0; i < nRawLength; i++)
414     {
415         c = pRaw[i];
416         if (c == L',' || c == L';')
417             isSep = TRUE;
418 
419         if (isSep == TRUE)
420         {
421             if ((isLastSep == FALSE) && (i != 0) && (i != nRawLength - 1))
422                 nLength++;
423         }
424         else
425         {
426             nLength++;
427 
428             if (isLastSep == TRUE || (isLastSep == FALSE && i == 0))
429                 nArgs++;
430         }
431 
432         isLastSep = isSep;
433         isSep = FALSE;
434     }
435 
436     nLength++;
437 
438     /* Leave, if there are no workstations in the list */
439     if (nArgs == 0)
440     {
441         pWorkstationsList = NULL;
442         return NERR_Success;
443     }
444 
445     /* Fail if there are more than eight workstations in the list */
446     if (nArgs > 8)
447         return 3951;
448 
449     /* Allocate the buffer for the clean workstation list */
450     pList = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nLength * sizeof(WCHAR));
451     if (pList == NULL)
452         return ERROR_NOT_ENOUGH_MEMORY;
453 
454     /* Build the clean workstation list */
455     isLastSep = FALSE;
456     isSep = FALSE;
457     nRawLength = wcslen(pRaw);
458     for (i = 0, j = 0; i < nRawLength; i++)
459     {
460         c = pRaw[i];
461         if (c == L',' || c == L';')
462             isSep = TRUE;
463 
464         if (isSep == TRUE)
465         {
466             if ((isLastSep == FALSE) && (i != 0) && (i != nRawLength - 1))
467             {
468                 pList[j] = L',';
469                 j++;
470             }
471         }
472         else
473         {
474             pList[j] = c;
475             j++;
476 
477             if (isLastSep == TRUE || (isLastSep == FALSE && i == 0))
478                 nArgs++;
479         }
480 
481         isLastSep = isSep;
482         isSep = FALSE;
483     }
484 
485     *pWorkstationsList = pList;
486 
487     return NERR_Success;
488 }
489 
490 
491 static
492 BOOL
493 ReadNumber(
494     PWSTR *s,
495     PWORD pwValue)
496 {
497     if (!iswdigit(**s))
498         return FALSE;
499 
500     while (iswdigit(**s))
501     {
502         *pwValue = *pwValue * 10 + **s - L'0';
503         (*s)++;
504     }
505 
506     return TRUE;
507 }
508 
509 
510 static
511 BOOL
512 ReadSeparator(
513     PWSTR *s)
514 {
515     if (**s == L'/' || **s == L'.')
516     {
517         (*s)++;
518         return TRUE;
519     }
520 
521     return FALSE;
522 }
523 
524 
525 static
526 BOOL
527 ParseDate(
528     PWSTR s,
529     PULONG pSeconds)
530 {
531     SYSTEMTIME SystemTime = {0};
532     FILETIME LocalFileTime, FileTime;
533     LARGE_INTEGER Time;
534     INT nDateFormat = 0;
535     PWSTR p = s;
536 
537     if (!*s)
538         return FALSE;
539 
540     GetLocaleInfoW(LOCALE_USER_DEFAULT,
541                    LOCALE_IDATE,
542                    (PWSTR)&nDateFormat,
543                    sizeof(INT));
544 
545     switch (nDateFormat)
546     {
547         case 0: /* mmddyy */
548         default:
549             if (!ReadNumber(&p, &SystemTime.wMonth))
550                 return FALSE;
551             if (!ReadSeparator(&p))
552                 return FALSE;
553             if (!ReadNumber(&p, &SystemTime.wDay))
554                 return FALSE;
555             if (!ReadSeparator(&p))
556                 return FALSE;
557             if (!ReadNumber(&p, &SystemTime.wYear))
558                 return FALSE;
559             break;
560 
561         case 1: /* ddmmyy */
562             if (!ReadNumber(&p, &SystemTime.wDay))
563                 return FALSE;
564             if (!ReadSeparator(&p))
565                 return FALSE;
566             if (!ReadNumber(&p, &SystemTime.wMonth))
567                 return FALSE;
568             if (!ReadSeparator(&p))
569                 return FALSE;
570             if (!ReadNumber(&p, &SystemTime.wYear))
571                 return FALSE;
572             break;
573 
574         case 2: /* yymmdd */
575             if (!ReadNumber(&p, &SystemTime.wYear))
576                 return FALSE;
577             if (!ReadSeparator(&p))
578                 return FALSE;
579             if (!ReadNumber(&p, &SystemTime.wMonth))
580                 return FALSE;
581             if (!ReadSeparator(&p))
582                 return FALSE;
583             if (!ReadNumber(&p, &SystemTime.wDay))
584                 return FALSE;
585             break;
586     }
587 
588     /* if only entered two digits: */
589     /*   assume 2000's if value less than 80 */
590     /*   assume 1900's if value greater or equal 80 */
591     if (SystemTime.wYear <= 99)
592     {
593         if (SystemTime.wYear >= 80)
594             SystemTime.wYear += 1900;
595         else
596             SystemTime.wYear += 2000;
597     }
598 
599     if (!SystemTimeToFileTime(&SystemTime, &LocalFileTime))
600         return FALSE;
601 
602     if (!LocalFileTimeToFileTime(&LocalFileTime, &FileTime))
603         return FALSE;
604 
605     Time.u.LowPart = FileTime.dwLowDateTime;
606     Time.u.HighPart = FileTime.dwHighDateTime;
607 
608     if (!RtlTimeToSecondsSince1970(&Time, pSeconds))
609         return FALSE;
610 
611     return TRUE;
612 }
613 
614 
615 INT
616 cmdUser(
617     INT argc,
618     WCHAR **argv)
619 {
620     INT i, j;
621     INT result = 0;
622     BOOL bAdd = FALSE;
623     BOOL bDelete = FALSE;
624 #if 0
625     BOOL bDomain = FALSE;
626 #endif
627     BOOL bRandomPassword = FALSE;
628     LPWSTR lpUserName = NULL;
629     LPWSTR lpPassword = NULL;
630     PUSER_INFO_4 pUserInfo = NULL;
631     USER_INFO_4 UserInfo;
632     LPWSTR pWorkstations = NULL;
633     LPWSTR p;
634     LPWSTR endptr;
635     DWORD value;
636     BOOL bPasswordAllocated = FALSE;
637     NET_API_STATUS Status;
638 
639     if (argc == 2)
640     {
641         Status = EnumerateUsers();
642         ConPrintf(StdOut, L"Status: %lu\n", Status);
643         return 0;
644     }
645     else if (argc == 3)
646     {
647         Status = DisplayUser(argv[2]);
648         ConPrintf(StdOut, L"Status: %lu\n", Status);
649         return 0;
650     }
651 
652     i = 2;
653     if (argv[i][0] != L'/')
654     {
655         lpUserName = argv[i];
656 //        ConPrintf(StdOut, L"User: %s\n", lpUserName);
657         i++;
658     }
659 
660     if (argv[i][0] != L'/')
661     {
662         lpPassword = argv[i];
663 //        ConPrintf(StdOut, L"Password: %s\n", lpPassword);
664         i++;
665     }
666 
667     for (j = i; j < argc; j++)
668     {
669         if (_wcsicmp(argv[j], L"/help") == 0)
670         {
671             ConResPuts(StdOut, IDS_USER_HELP);
672             return 0;
673         }
674         else if (_wcsicmp(argv[j], L"/add") == 0)
675         {
676             bAdd = TRUE;
677         }
678         else if (_wcsicmp(argv[j], L"/delete") == 0)
679         {
680             bDelete = TRUE;
681         }
682         else if (_wcsicmp(argv[j], L"/domain") == 0)
683         {
684             ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/DOMAIN");
685 #if 0
686             bDomain = TRUE;
687 #endif
688         }
689         else if (_wcsicmp(argv[j], L"/random") == 0)
690         {
691             bRandomPassword = TRUE;
692             GenerateRandomPassword(&lpPassword,
693                                    &bPasswordAllocated);
694         }
695     }
696 
697     if (bAdd && bDelete)
698     {
699         result = 1;
700         goto done;
701     }
702 
703     /* Interactive password input */
704     if (lpPassword != NULL && wcscmp(lpPassword, L"*") == 0)
705     {
706         ReadPassword(&lpPassword,
707                      &bPasswordAllocated);
708     }
709 
710     if (!bAdd && !bDelete)
711     {
712         /* Modify the user */
713         Status = NetUserGetInfo(NULL,
714                                 lpUserName,
715                                 4,
716                                 (LPBYTE*)&pUserInfo);
717         if (Status != NERR_Success)
718         {
719             ConPrintf(StdOut, L"Status: %lu\n", Status);
720             result = 1;
721             goto done;
722         }
723     }
724     else if (bAdd && !bDelete)
725     {
726         /* Add the user */
727         ZeroMemory(&UserInfo, sizeof(USER_INFO_4));
728 
729         UserInfo.usri4_name = lpUserName;
730         UserInfo.usri4_password = lpPassword;
731         UserInfo.usri4_flags = UF_SCRIPT | UF_NORMAL_ACCOUNT;
732         UserInfo.usri4_acct_expires = TIMEQ_FOREVER;
733         UserInfo.usri4_primary_group_id = DOMAIN_GROUP_RID_USERS;
734 
735         pUserInfo = &UserInfo;
736     }
737 
738     for (j = i; j < argc; j++)
739     {
740         if (_wcsnicmp(argv[j], L"/active:", 8) == 0)
741         {
742             p = &argv[i][8];
743             if (_wcsicmp(p, L"yes") == 0)
744             {
745                 pUserInfo->usri4_flags &= ~UF_ACCOUNTDISABLE;
746             }
747             else if (_wcsicmp(p, L"no") == 0)
748             {
749                 pUserInfo->usri4_flags |= UF_ACCOUNTDISABLE;
750             }
751             else
752             {
753                 ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/ACTIVE");
754                 result = 1;
755                 goto done;
756             }
757         }
758         else if (_wcsnicmp(argv[j], L"/comment:", 9) == 0)
759         {
760             pUserInfo->usri4_comment = &argv[j][9];
761         }
762         else if (_wcsnicmp(argv[j], L"/countrycode:", 13) == 0)
763         {
764             p = &argv[i][13];
765             value = wcstoul(p, &endptr, 10);
766             if (*endptr != 0)
767             {
768                 ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/COUNTRYCODE");
769                 result = 1;
770                 goto done;
771             }
772 
773             /* FIXME: verify the country code */
774 
775             pUserInfo->usri4_country_code = value;
776         }
777         else if (_wcsnicmp(argv[j], L"/expires:", 9) == 0)
778         {
779             p = &argv[i][9];
780             if (_wcsicmp(p, L"never") == 0)
781             {
782                 pUserInfo->usri4_acct_expires = TIMEQ_FOREVER;
783             }
784             else if (!ParseDate(p, &pUserInfo->usri4_acct_expires))
785             {
786                 ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/EXPIRES");
787                 result = 1;
788                 goto done;
789 
790                 /* FIXME: Parse the date */
791 //                ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/EXPIRES");
792             }
793         }
794         else if (_wcsnicmp(argv[j], L"/fullname:", 10) == 0)
795         {
796             pUserInfo->usri4_full_name = &argv[j][10];
797         }
798         else if (_wcsnicmp(argv[j], L"/homedir:", 9) == 0)
799         {
800             pUserInfo->usri4_home_dir = &argv[j][9];
801         }
802         else if (_wcsnicmp(argv[j], L"/passwordchg:", 13) == 0)
803         {
804             p = &argv[i][13];
805             if (_wcsicmp(p, L"yes") == 0)
806             {
807                 pUserInfo->usri4_flags &= ~UF_PASSWD_CANT_CHANGE;
808             }
809             else if (_wcsicmp(p, L"no") == 0)
810             {
811                 pUserInfo->usri4_flags |= UF_PASSWD_CANT_CHANGE;
812             }
813             else
814             {
815                 ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/PASSWORDCHG");
816                 result = 1;
817                 goto done;
818             }
819         }
820         else if (_wcsnicmp(argv[j], L"/passwordreq:", 13) == 0)
821         {
822             p = &argv[i][13];
823             if (_wcsicmp(p, L"yes") == 0)
824             {
825                 pUserInfo->usri4_flags &= ~UF_PASSWD_NOTREQD;
826             }
827             else if (_wcsicmp(p, L"no") == 0)
828             {
829                 pUserInfo->usri4_flags |= UF_PASSWD_NOTREQD;
830             }
831             else
832             {
833                 ConResPrintf(StdErr, IDS_ERROR_INVALID_OPTION_VALUE, L"/PASSWORDREQ");
834                 result = 1;
835                 goto done;
836             }
837         }
838         else if (_wcsnicmp(argv[j], L"/profilepath:", 13) == 0)
839         {
840             pUserInfo->usri4_profile = &argv[j][13];
841         }
842         else if (_wcsnicmp(argv[j], L"/scriptpath:", 12) == 0)
843         {
844             pUserInfo->usri4_script_path = &argv[j][12];
845         }
846         else if (_wcsnicmp(argv[j], L"/times:", 7) == 0)
847         {
848             /* FIXME */
849             ConResPrintf(StdErr, IDS_ERROR_OPTION_NOT_SUPPORTED, L"/TIMES");
850         }
851         else if (_wcsnicmp(argv[j], L"/usercomment:", 13) == 0)
852         {
853             pUserInfo->usri4_usr_comment = &argv[j][13];
854         }
855         else if (_wcsnicmp(argv[j], L"/workstations:", 14) == 0)
856         {
857             p = &argv[i][14];
858             if (wcscmp(p, L"*") == 0 || wcscmp(p, L"") == 0)
859             {
860                 pUserInfo->usri4_workstations = NULL;
861             }
862             else
863             {
864                 Status = BuildWorkstationsList(&pWorkstations, p);
865                 if (Status == NERR_Success)
866                 {
867                     pUserInfo->usri4_workstations = pWorkstations;
868                 }
869                 else
870                 {
871                     ConPrintf(StdOut, L"Status %lu\n\n", Status);
872                     result = 1;
873                     goto done;
874                 }
875             }
876         }
877     }
878 
879     if (!bAdd && !bDelete)
880     {
881         /* Modify the user */
882         Status = NetUserSetInfo(NULL,
883                                 lpUserName,
884                                 4,
885                                 (LPBYTE)pUserInfo,
886                                 NULL);
887         ConPrintf(StdOut, L"Status: %lu\n", Status);
888     }
889     else if (bAdd && !bDelete)
890     {
891         /* Add the user */
892         Status = NetUserAdd(NULL,
893                             4,
894                             (LPBYTE)pUserInfo,
895                             NULL);
896         ConPrintf(StdOut, L"Status: %lu\n", Status);
897     }
898     else if (!bAdd && bDelete)
899     {
900         /* Delete the user */
901         Status = NetUserDel(NULL,
902                             lpUserName);
903         ConPrintf(StdOut, L"Status: %lu\n", Status);
904     }
905 
906     if (Status == NERR_Success &&
907         lpPassword != NULL &&
908         bRandomPassword == TRUE)
909     {
910         ConPrintf(StdOut, L"The password for %s is: %s\n", lpUserName, lpPassword);
911     }
912 
913 done:
914     if (pWorkstations != NULL)
915         HeapFree(GetProcessHeap(), 0, pWorkstations);
916 
917     if ((bPasswordAllocated == TRUE) && (lpPassword != NULL))
918         HeapFree(GetProcessHeap(), 0, lpPassword);
919 
920     if (!bAdd && !bDelete && pUserInfo != NULL)
921         NetApiBufferFree(pUserInfo);
922 
923     if (result != 0)
924     {
925         ConResPuts(StdOut, IDS_GENERIC_SYNTAX);
926         ConResPuts(StdOut, IDS_USER_SYNTAX);
927     }
928 
929     return result;
930 }
931 
932 /* EOF */
933