User Management using API calls: Difference between revisions
CancerFace (talk | contribs) (→Macro) |
CancerFace (talk | contribs) (→Macros) |
||
Line 191: | Line 191: | ||
# Add the user to the group | # Add the user to the group | ||
System::Call 'netapi32::NetLocalGroupAddMembers(w "${SERVER_NAME}",i R9,i 0,*i R8,i 1)i .r0' | System::Call 'netapi32::NetLocalGroupAddMembers(w "${SERVER_NAME}",i R9,i 0,*i R8,i 1)i .r0' | ||
System:: | System::Free $R8 | ||
System::Free $R9 | System::Free $R9 | ||
!macroend | !macroend |
Revision as of 11:02, 3 February 2007
Author: CancerFace (talk, contrib) |
NSIS forum thread
Description
Here is a list of macros that can be used to manipulate user and group accounts on a windows machine. All macros use API calls to netapi32.dll and/or advapi32.dll. The code is provided without any error checking so you may want to test it in your installer scenario carefully. It is assumed that the installer will run using admin privileges in order to create/modify user/group accounts. You should add some code to check that these privileges are active in order to avoid problems.
Macros for user/group manipulation
Get Server Name
This macro will obtain the name of the computer that the installer is running. An attempt to determine the NetBios name will be carried out first using the GetComputerNameEx API call and if it is not successful then the GetComputerName API call will be established. If both fail then the computer name will be set to null. Note that this is not a problem since for all NetApi32 calls a null computer name implies the local computer.
Usage
Use the following code in your NSIS installer to get the computer name to some variable ${VAR}:
!include "LogicLib.nsh" !insertmacro GetServerName $VAR
Macro
!macro GetServerName SERVER_NAME_OUT System::Call 'kernel32.dll::GetComputerNameExW(i 4,w .r0,*i ${NSIS_MAX_STRLEN} r1)i.r2' ${If} $2 = 1 StrCpy ${SERVER_NAME_OUT} "\\$0" ${Else} System::Call "kernel32.dll::GetComputerNameW(t .r0,*i ${NSIS_MAX_STRLEN} r1)i.r2" ${If} $2 = 1 StrCpy ${SERVER_NAME_OUT} "\\$0" ${Else} StrCpy ${SERVER_NAME_OUT} "" ${EndIf} ${EndIf} !macroend
Create User
User creation can be achieved using the NetUserAdd API call
Usage
From within your NSIS code call the macro providing the appropriate parameters. For the GROUP_ID parameter use one of the following values (needed to construct the equivalent well-known group SID):
Administrators | S-1-5-32-544 | (use 544) |
Users | S-1-5-32-545 | (use 545) |
Guests | S-1-5-32-546 | (use 546) |
Power Users | S-1-5-32-547 | (use 547) |
!insertmacro CreateUser "\\ComputerName" "jdoe" "password" "Some User" "John" "Doe" 545
Macro
!macro CreateUser SERVER_NAME USERNAME PASSWORD DESCRIPTION NAME SURNAME GROUP_ID # Create the user System::Call '*(w "${USERNAME}",w "${PASSWORD}",i n,i 1,w n,w "${DESCRIPTION}",i 513,w n)i.R0' System::Call 'netapi32::NetUserAdd(w "${SERVER_NAME}",i 1,i R0,*i.r0) i.r1' System::Free $R0 # Set the user's name and surname System::Call '*(w "${NAME} ${SURNAME}")i.R0' System::Call 'netapi32::NetUserSetInfo(w "${SERVER_NAME}",w "${USERNAME}",i 1011, \ i R0,*i.r0)i.r1' System::Free $R0 # Get the user's SID in $R8 System::Call '*(&w${NSIS_MAX_STRLEN})i.R8' System::Call 'advapi32::LookupAccountNameW(w "${SERVER_NAME}",w "${USERNAME}",i R8, \ *i ${NSIS_MAX_STRLEN}, w .R1, *i ${NSIS_MAX_STRLEN}, *i .r0)i .r1' System::Call 'advapi32::ConvertSidToStringSid(i R8,*t .R1)i .r0' # Get the group name in $R9 using its SID System::Call '*(&i1 0,&i4 0,&i1 5)i.R0' System::Call 'advapi32::AllocateAndInitializeSid(i R0,i 2,i 32,i ${GROUP_ID},i 0, \ i 0,i 0,i 0,i 0,i 0,*i .r2)' System::Free $R0 System::Call '*(&w${NSIS_MAX_STRLEN})i.R9' System::Call 'advapi32::LookupAccountSidW(i 0,i r2,i R9,*i ${NSIS_MAX_STRLEN},t .r3, \ *i ${NSIS_MAX_STRLEN},*i .r4)' System::Call 'advapi32::FreeSid(i r2)' # Add the user to the user's group System::Call 'netapi32::NetLocalGroupAddMembers(w "${SERVER_NAME}",i R9,i 0,*i R8,i 1)i .r0' System::Free $R8 System::Free $R9 !macroend
Delete User
This macro uses the NetUserDel API call to delete a user from the local system
Usage
Call the macro from your NSIS script and pass the name of the user to be deleted:
!insertmacro DeleteUser "\\ComputerName" "jdoe"
Macro
!macro DeleteUser SERVER_NAME USERNAME # Delete a user System::Call 'netapi32::NetUserDel(w "${SERVER_NAME}",w "${USERNAME}")i.r0' !macroend
Create Group
This macro uses the NetLocalGroupAdd API call to create a new group
Usage
From within your NSIS code call the macro providing the appropriate parameters:
!insertmacro CreateGroup "\\ComputerName" "NewGroup" "Some Description for the group"
Macro
!macro CreateGroup SERVER_NAME GROUP_NAME GROUP_DESCRIPTION # Create a local group System::Call '*(w "${GROUP_NAME}",w "${GROUP_DESCRIPTION}")i.R0' System::Call 'netapi32::NetLocalGroupAdd(w "${SERVER_NAME}",i 1,i R0,*i r0)i.r1' System::Free $R0 !macroend
Delete Group
This macro usee the NetLocalGroupDel API call to delete a group from the local machine
Usage
Call the macro from your NSIS installer:
!insertmacro "\\ComputerName" "SomeGroupName"
Macro
!macro DeleteGroup SERVER_NAME GROUP_NAME # Delete a local group System::Call 'netapi32::NetLocalGroupDel(w "${SERVER_NAME}",w "${GROUP_NAME}")i.r3' !macroend
Add User to a group
Here are two macros to add a user to a group using the NetLocalGroupAddMembers API call. The first macro uses the SID of a group (constructed from the well-known SID) and the second macro uses the group name. The advantage of using a well-known SID is that the macro will work on any system regardless of the system's language. For example the english version of windows has a group called 'Users' while in the french version this group is called 'Utilisateurs'. In both versions the SID of this particular group is S-1-5-32-545.
Usage
Call the macros from your NSIS script providing sufficient information. If you use the first macro then use one of the following values for the group SID:
Administrators | 544 |
Users | 545 |
Guests | 546 |
Power Users | 547 |
!insertmacro AddUserToGroup1 "\\ComputerName" "jdoe" "547"
Call the second macro providing the name of the group:
!insertmacro AddUserToGroup2 "\\ComputerName" "jdoe" "Power Users"
Macros
!macro AddUserToGroup1 SERVER_NAME USERNAME GROUP_ID # Add a user to a group using the group's SID (from well-known SIDs) # Administrators S-1-5-32-544 (use 544) # Users S-1-5-32-545 (use 545) # Guests S-1-5-32-546 (use 546) # Power Users S-1-5-32-547 (use 547) # Get the user's SID System::Call '*(&w${NSIS_MAX_STRLEN})i.R8' System::Call 'advapi32::LookupAccountNameW(w "${SERVER_NAME}",w "${USERNAME}",i R8, \ *i ${NSIS_MAX_STRLEN}, w .R1, *i ${NSIS_MAX_STRLEN}, *i .r0)i .r1' System::Call 'advapi32::ConvertSidToStringSid(i R8,*t .R1)i .r0' # Get the group's SID System::Call '*(&i1 0,&i4 0,&i1 5)i.R0' System::Call 'advapi32::AllocateAndInitializeSid(i R0,i 2,i 32,i ${GROUP_ID},i 0, \ i 0,i 0,i 0,i 0,i 0,*i .r2)' System::Free $R0 System::Call '*(&w${NSIS_MAX_STRLEN})i.R9' System::Call 'advapi32::LookupAccountSidW(i 0,i r2,i R9,*i ${NSIS_MAX_STRLEN},t .r3, \ *i ${NSIS_MAX_STRLEN},*i .r4)' System::Call 'advapi32::FreeSid(i r2)' # Add the user to the group System::Call 'netapi32::NetLocalGroupAddMembers(w "${SERVER_NAME}",i R9,i 0,*i R8,i 1)i .r0' System::Free $R8 System::Free $R9 !macroend
!macro AddUserToGroup2 SERVER_NAME USERNAME GROUP_NAME # Add a user to a group using the group's name # Get the user's SID System::Call '*(&w${NSIS_MAX_STRLEN})i.R8' System::Call 'advapi32::LookupAccountNameW(w "${SERVER_NAME}",w "${USERNAME}",i R8, \ *i ${NSIS_MAX_STRLEN}, w .R1, *i ${NSIS_MAX_STRLEN}, *i .r0)i .r1' System::Call 'advapi32::ConvertSidToStringSid(i R8,*t .R1)i .r0' # Add the user to the group System::Call 'netapi32::NetLocalGroupAddMembers(w "${SERVER_NAME}",w "${GROUP_NAME}", \ i 0,*i R8,i 1)i .r0' System::Free $R8 !macroend
Delete User from a group
Deleting a user from a group is achieved by using the NetLocalGroupDelMembers API call. The first macro uses the SID of a group (constructed from the well-known SID) and the second macro uses the group name.
Usage
Call the macros from your NSIS script providing sufficient information. If you use the first macro then use one of the following values for the group SID:
Administrators | 544 |
Users | 545 |
Guests | 546 |
Power Users | 547 |
!insertmacro DeleteUserFromGroup1 "\\ComputerName" "jdoe" "547"
Call the second macro providing the name of the group:
!insertmacro DeleteUserFromGroup2 "\\ComputerName" "jdoe" "Power Users"
Macros
!macro DeleteUserFromGroup1 SERVER_NAME USERNAME GROUP_ID # Delete a user from a group using the group's SID # Administrators S-1-5-32-544 (use 544) # Users S-1-5-32-545 (use 545) # Guests S-1-5-32-546 (use 546) # Power Users S-1-5-32-547 (use 547) # Get the user's SID System::Call '*(&w${NSIS_MAX_STRLEN})i.R8' System::Call 'advapi32::LookupAccountNameW(w "${SERVER_NAME}",w "${USERNAME}",i R8, \ *i ${NSIS_MAX_STRLEN}, w .R1, *i ${NSIS_MAX_STRLEN}, *i .r0)i .r1' System::Call 'advapi32::ConvertSidToStringSid(i R8,*t .R1)i .r0' # Get the group's SID System::Call '*(&i1 0,&i4 0,&i1 5)i.R0' System::Call 'advapi32::AllocateAndInitializeSid(i R0,i 2,i 32,i ${GROUP_ID},i 0,i 0, \ i 0,i 0,i 0,i 0,*i .r2)' System::Free $R0 System::Call '*(&w${NSIS_MAX_STRLEN})i.R9' System::Call 'advapi32::LookupAccountSidW(i 0,i r2,i R9,*i ${NSIS_MAX_STRLEN},t .r3, \ *i ${NSIS_MAX_STRLEN},*i .r4)' System::Call 'advapi32::FreeSid(i r2)' # Delete the user System::Call 'netapi32::NetLocalGroupDelMembers(w "${SERVER_NAME}", i R9, i 0, *i R8, \ i 1) i.r6' System::Free $R8 System::Free $R9 !macroend
!macro DeleteUserFromGroup2 SERVER_NAME USERNAME GROUP_NAME # Delete a user from a group using the group's name # Get the user's SID System::Call '*(&w${NSIS_MAX_STRLEN})i.R8' System::Call 'advapi32::LookupAccountNameW(w "${SERVER_NAME}",w "${USERNAME}",i R8, \ *i ${NSIS_MAX_STRLEN},w .R1,*i ${NSIS_MAX_STRLEN},*i .r0)i .r1' System::Call 'advapi32::ConvertSidToStringSid(i R8,*t .R1)i .r0' # Delete the user System::Call 'netapi32::NetLocalGroupDelMembers(w "${SERVER_NAME}", w "${GROUP_NAME}", \ i 0,*i R8,i 1)i.r6' System::Free $R8 !macroend
Get information for a group
Obtaining information for a group, such as its name and comment, can be achieved by calling the NetLocalGroupGetInfo API call.
Usage
Call the macro from your NSIS installer passing two variables that will accept the name and comment respectively:
Var "Var1" Var "Var2" !insertmacro GetGroupInfo "\\ComputerName" "Power Users" $Var1 $Var2
Macro
!macro GetGroupInfo SERVER_NAME GROUP_NAME NAME_OUT COMMENT_OUT # Obtain the group name and comment System::Call 'netapi32::NetLocalGroupGetInfo(w "${SERVER_NAME}",w "${GROUP_NAME}", \ i 1,*i.R0)i.r1' System::Call '*$R0(w .R1,w .R2)' StrCpy ${NAME_OUT} '$R1' StrCpy ${COMMENT_OUT} '$R2' System::Free $R0 !macroend
Set group information
It is possible to change a group's name and description using the NetLocalGroupSetInfo API call
Usage
Call the macro from your NSIS script providing all the necessary information
!insertmacro SetGroupInfo "\\ComputerName" "MyGroup" "My Renamed Group" "New Group Description"
Macro
!macro SetGroupInfo SERVER_NAME GROUP_NAME NEW_GROUP_NAME NEW_GROUP_DESCRIPTION # Set a group's name System::Call '*(w "${NEW_GROUP_NAME}")i.R0' System::Call 'netapi32::NetLocalGroupSetInfo(w "${SERVER_NAME}",w "${GROUP_NAME}",i 0, \ i R0,*i r0)i.r1' System::Free $R0 # Set a group's name and description System::Call '*(w "${NEW_GROUP_NAME}",w "${NEW_GROUP_DESCRIPTION}")i.R0' System::Call 'netapi32::NetLocalGroupSetInfo(w "${SERVER_NAME}",w "${NEW_GROUP_NAME}", \ i 1,i R0,*i r0)i.r1' System::Free $R0 !macroend
Set user information
Using the NetUserSetInfo API call it is possible to change a user's username, password, name and surname, privileges etc.
Change user's username
This is done using the User Info Structure 0:
Usage
Call the macro from your NSIS installer providing a new name for the user:
!insertmacro RenameUser "\\ComputerName" "jdoe" "jdoe1"
Macro
!macro RenameUser SERVER_NAME USERNAME NEW_USERNAME # Change the username of a user System::Call '*(w "${NEW_USERNAME}")i.R0' System::Call 'netapi32::NetUserSetInfo(w "${SERVER_NAME}",w "${USERNAME}",i 0,i R0,*i.r0)i.r1' System::Free $R0 !macroend
Change user's password
This is done using the User Info Structure 1003:
Usage
Call the macro from your NSIS installer providing a new password for the user:
!insertmacro SetUserPassword "\\ComputerName" "jdoe" "newpassword"
Macro
!macro SetUserPassword SERVER_NAME USERNAME PASSWORD # Change a user's password System::Call '*(w "${PASSWORD}")i.R0' System::Call 'netapi32::NetUserSetInfo(w "${SERVER_NAME}",w "${USERNAME}",i 1003, \ i R0,*i.r0)i.r1' System::Free $R0 !macroend
Change user's home directory
This is done using the User Info Structure 1006:
Usage
Call the macro from your NSIS installer providing a new home directory for the user:
!insertmacro SetUserHomeDir "\\ComputerName" "jdoe" "F:\My Documents"
Macro
!macro SetUserHomeDir SERVER_NAME USERNAME HOME_PATH # Change a user's password System::Call '*(w "${HOME_PATH}")i.R0' System::Call 'netapi32::NetUserSetInfo(w "${SERVER_NAME}",w "${USERNAME}",i 1006, \ i R0,*i.r0)i.r1' System::Free $R0 !macroend
Change user's comment
This is done using the User Info Structure 1007:
Usage
Call the macro from your NSIS installer providing a new home comment for the user:
!insertmacro SetUserComment "\\ComputerName" "jdoe" "jdoe's new comment"
Macro
!macro SetUserComment SERVER_NAME USERNAME COMMENT # Change a user's comment System::Call '*(w "${COMMENT}")i.R0' System::Call 'netapi32::NetUserSetInfo(w "${SERVER_NAME}",w "${USERNAME}",i 1007, \ i R0,*i.r0)i.r1' System::Free $R0 !macroend
Change user's name and surname
This is done using the User Info Structure 1011:
Usage
Call the macro from your NSIS installer providing a new name and surname for the user:
!insertmacro SetUserNameAndSurname "\\ComputerName" "jdoe" "John" "Doe"
Macro
!macro SetUserNameAndSurname SERVER_NAME USERNAME NAME SURNAME # Change a user's Name and Surname System::Call '*(w "${NAME} ${SURNAME}")i.R0' System::Call 'netapi32::NetUserSetInfo(w "${SERVER_NAME}",w "${USERNAME}",i 1011, \ i R0,*i.r0)i.r1' System::Free $R0 !macroend
Change user's attributes
This is done using the User Info Structure 1008:
Usage
Call the macro from your NSIS installer providing the new user attributes. You need to define the possible attributes and then add the ones that you want the user to have:
!define UF_SCRIPT 0x000001 !define UF_ACCOUNTDISABLE 0x000002 !define UF_HOMEDIR_REQUIRED 0x000008 !define UF_LOCKOUT 0x000010 !define UF_PASSWD_NOTREQD 0x000020 !define UF_PASSWD_CANT_CHANGE 0x000040 !define UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED 0x000080 !define UF_TEMP_DUPLICATE_ACCOUNT 0x000100 !define UF_NORMAL_ACCOUNT 0x000200 !define UF_INTERDOMAIN_TRUST_ACCOUNT 0x000800 !define UF_WORKSTATION_TRUST_ACCOUNT 0x001000 !define UF_SERVER_TRUST_ACCOUNT 0x002000 !define UF_DONT_EXPIRE_PASSWD 0x010000 !define UF_MNS_LOGON_ACCOUNT 0x020000 !define UF_SMARTCARD_REQUIRED 0x040000 !define UF_TRUSTED_FOR_DELEGATION 0x080000 !define UF_NOT_DELEGATED 0x100000 !define UF_USE_DES_KEY_ONLY 0x200000 !define UF_DONT_REQUIRE_PREAUTH 0x400000 !define UF_PASSWORD_EXPIRED 0x800000 # The first two should always be there StrCpy $0 0 IntOp $0 $0 + ${UF_NORMAL_ACCOUNT} IntOp $0 $0 + ${UF_SCRIPT} # Use IntOp to add more from the above list of definitions !insertmacro SetUserAttributes "\\ComputerName" "jdoe" "$0"
Macro
!macro SetUserAttributes SERVER_NAME USERNAME ATTRIBUTES # Change user account attributes System::Call '*(i "${ATTRIBUTES}")i.R0' System::Call 'netapi32::NetUserSetInfo(w "${SERVER_NAME}",w "${USERNAME}",i 1008, \ i R0,*i.r0)i.r1' System::Free $R0 !macroend
Enumerate all users
This is achieved by calling the NetUserEnum API function We can create an array with all the users using Afrow UK's NSISArray Plugin
Usage
Call the macro from your NSIS installer providing a name for the user array that will be generated by the macro
!insertmacro EnumerateUsers "\\ComputerName" "LocalUserArray"
Macro
!macro EnumerateUsers SERVER_NAME USER_ARRAY_NAME # Enumerate the local users !define Index "Line${__LINE__}" # $R1 holds the number of entries processed # $R2 holds the total number of entries System::Call 'netapi32::NetUserEnum(w "${SERVER_NAME}",i 0,i 2,*i .R0,i ${NSIS_MAX_STRLEN}, \ *i .R1,*i .R2,*i .r1)i .r2' StrCpy $R8 $R0 # dump them into an array object StrCpy $9 0 NSISArray::New /NOUNLOAD ${USER_ARRAY_NAME} ${Index}-loop: StrCmp $9 $R2 ${Index}-stop +1 System::Call "*$R0(w.R9)" NSISArray::Write /NOUNLOAD ${USER_ARRAY_NAME} $9 "$R9" IntOp $R0 $R0 + 4 IntOp $9 $9 + 1 Goto ${Index}-loop ${Index}-stop: NSISArray::SizeOf /NOUNLOAD ${USER_ARRAY_NAME} Pop $0 StrCmp $0 $R2 +2 +1 MessageBox MB_OK|MB_ICONEXCLAMATION 'Could not place all the user accounts into an array!' System::Call 'netapi32.dll::NetApiBufferFree(i R8)i .R1' !undef Index !macroend
Enumerate all groups
This is achieved by calling the NetLocalGroupEnum API function We can create an array with all the groups using Afrow UK's NSISArray Plugin
Usage
Call the macro from your NSIS installer providing a name for the group array that will be generated by the macro
!insertmacro EnumerateGroups "\\ComputerName" "LocalGroupArray"
Macro
!macro EnumerateGroups SERVER_NAME GROUP_ARRAY_NAME # Enumerate the loacl groups !define Index "Line${__LINE__}" # $R1 holds the number of entries processed # $R2 holds the total number of entries System::Call 'netapi32::NetLocalGroupEnum(w "${SERVER_NAME}",i 0,*i .R0,i ${NSIS_MAX_STRLEN}, \ *i .R1,*i .R2,*i .R3)i .R4' StrCpy $R8 $R0 # dump them into an array object NSISArray::New /NOUNLOAD ${GROUP_ARRAY_NAME} ${Index}-loop: StrCmp $9 $R2 ${Index}-stop +1 System::Call "*$R0(w.R9)" NSISArray::Write /NOUNLOAD ${GROUP_ARRAY_NAME} $9 "$R9" IntOp $R0 $R0 + 4 IntOp $9 $9 + 1 Goto ${Index}-loop ${Index}-stop: NSISArray::SizeOf /NOUNLOAD ${GROUP_ARRAY_NAME} Pop $0 StrCmp $0 $R2 +2 +1 MessageBox MB_OK|MB_ICONEXCLAMATION 'Could not place all the groups into an array!' System::Call 'netapi32::NetApiBufferFree(i R8)i.r2' !undef Index !macroend
Get User’s Group(s)
This is achieved by calling the NetUserGetLocalGroups API function We can create an array with all the groups that a user belongs to using Afrow UK's NSISArray Plugin
Usage
Call the macro from your NSIS installer providing a name for the user array that will be generated by the macro
!insertmacro EnumerateUsers "\\ComputerName" "UserName" "LocalUserGroupArray"
Macro
!macro GetUserGroups SERVER_NAME USERNAME GROUP_ARRAY_NAME # Get user's group(s) !define Index "Line${__LINE__}" # $R0 buffer with an array of LOCALGROUP_USERS_INFO_0 structures # $R1 holds the number of entries processed # $R2 holds the total number of entries System::Call 'netapi32::NetUserGetLocalGroups(w "${SERVER_NAME}",w "${USERNAME}",i 0, \ i 0,*i.R0,i ${NSIS_MAX_STRLEN},*i.R1,*i.R2)i.r2' StrCpy $R8 $R0 StrCpy $9 0 NSISArray::New /NOUNLOAD ${GROUP_ARRAY_NAME} ${Index}-loop: StrCmp $9 $R2 ${Index}-stop +1 System::Call "*$R0(w.R9)" NSISArray::Write /NOUNLOAD ${GROUP_ARRAY_NAME} $9 "$R9" IntOp $R0 $R0 + 4 IntOp $9 $9 + 1 Goto ${Index}-loop ${Index}-stop: NSISArray::SizeOf /NOUNLOAD ${GROUP_ARRAY_NAME} Pop $0 StrCmp $0 $R2 +2 +1 MessageBox MB_OK|MB_ICONEXCLAMATION "Could not place all the user's groups into an array!" System::Call 'netapi32.dll::NetApiBufferFree(i R8)i .r2' !undef Index !macroend
Resources and Links
- NSIS forum thread
- User Manager Plugin
API Functions used:
- NetUserAdd
- NetUserDel
- NetLocalGroupAdd
- NetLocalGroupDel
- NetLocalGroupAddMembers
- NetLocalGroupDelMembers
- NetLocalGroupGetInfo
- NetLocalGroupSetInfo
- NetUserSetInfo
- NetUserEnum
- NetLocalGroupEnum
- LookupAccountName
- LookupAccountSID
- AllocateAndInitializeSID
- ConvertSidToStringSid
- FreeSID
- NetUserGetLocalGroups
CancerFace 12:08, 7 October 2006 (PDT)