blueboy
blueboy

Reputation: 64

Windows, add user by Rust

I use winapi NetUserAdd to add a user,account added succsess,execute command net user,shows as below picture and cannot find users by control panel.

enter image description here

What wrong with LPSWTR or USER_INFO_1 struct to LPBYTE?


use winapi::um::lmaccess::{USER_INFO_1,NetUserAdd,UF_SCRIPT};
use std::iter::{once};
use std::os::windows::ffi::OsStrExt;
use std::ffi::OsStr;

pub fn winstr(value: &str) -> Vec<u16> {
    OsStr::new(value).encode_wide().chain(once(0)).collect()
}

fn main() {

        let username:String =  "Test".to_string();
        let password:String = "Test******".to_string();
        let mut user = USER_INFO_1{
            usri1_name:winstr(&username).as_mut_ptr(),
            usri1_password:winstr(&password).as_mut_ptr(),
            usri1_priv:1,
            usri1_password_age: 0,
            usri1_home_dir: std::ptr::null_mut(),
            usri1_comment: std::ptr::null_mut(),
            usri1_flags:UF_SCRIPT,
            usri1_script_path: std::ptr::null_mut(),
        };
        
        let mut error = 0 ;
        unsafe{
            NetUserAdd(std::ptr::null_mut(),1,&mut user as *mut _ as _,&mut error);
        }
        
        println!("{}",error);//result is 0,means success.

}

Upvotes: 1

Views: 498

Answers (2)

Dilawar
Dilawar

Reputation: 5645

I’d recommend this excellent resource https://github.com/secur30nly/netuser-rs. It is a rust-based cli/library to manage Windows users and groups.

My solution is built on top of it but is slightly different: I used USER_INFO_2 instead of USER_INFO_1, which allows for the user's full name, and LOCALGROUP_MEMBER_INFO_3 instead of LOCALGROUP_MEMBER_INFO_1, which is a simpler API.

#[cfg(windows)]
fn create_account_win32<S: AsRef<str> + std::fmt::Display>(
    user_name: S,
    full_name: S,
    password: S,
    group: &str,
) -> anyhow::Result<()> {
    use windows::Win32::NetworkManagement::NetManagement::{
        NetUserAdd, UF_NORMAL_ACCOUNT, UF_SCRIPT, USER_ACCOUNT_FLAGS, USER_INFO_2, USER_PRIV_USER,
    };

    // use level 2 i.e. USER_INFO_2 in NetUserAdd
    // https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/ns-lmaccess-user_info_2
    let mut user = USER_INFO_2::default();

    let (user_name_w, _v1) = str_to_pwstr(user_name.as_ref());
    user.usri2_name = user_name_w;

    let (password_w, _v2) = str_to_pwstr(password.as_ref());
    user.usri2_password = password_w;

    let (full_name_w, _v3) = str_to_pwstr(full_name.as_ref());
    user.usri2_full_name = full_name_w;

    user.usri2_priv = USER_PRIV_USER;
    user.usri2_flags = UF_SCRIPT | USER_ACCOUNT_FLAGS(UF_NORMAL_ACCOUNT);
    user.usri2_acct_expires = u32::MAX;

    tracing::debug!("Creating user {user:?}");
    let status: u32 = unsafe { NetUserAdd(None, 2, &user as *const _ as _, None) };
    if status == 0 {
        println!("✓ Successfully created account.");
    } else {
        eprintln!("⛌  Failed to create account!");
    }

        // Now add user to a localgroup. This group must already exist in the system.
    manage_group_users_win32(user_name.as_ref(), group, false)?;

    Ok(())
}

/// Add user to a specified local group.
/// If the `deletion` param is `true` - delete the specified user account from the local
/// group.
#[cfg(windows)]
unsafe fn manage_group_users_win32(
    username: &str,
    groupname: &str,
    deletion: bool,
) -> anyhow::Result<()> {
    use windows::Win32::NetworkManagement::NetManagement::{
        NetLocalGroupAddMembers, NetLocalGroupDelMembers, LOCALGROUP_MEMBERS_INFO_3,
    };

    let (wide_groupname_nul, _buf) = str_to_pwstr(groupname);
    let (wide_username_nul, _buf) = str_to_pwstr(username);
    tracing::debug!("Mangaing groups of user {username}: group={groupname} , delete={deletion}.");

    let group_members = LOCALGROUP_MEMBERS_INFO_3 {
        lgrmi3_domainandname: wide_username_nul,
    };

    let rc = if deletion {
        NetLocalGroupDelMembers(
            None,
            wide_groupname_nul,
            3,
            &group_members as *const _ as *const u8,
            1,
        )
    } else {
        NetLocalGroupAddMembers(
            None,
            wide_groupname_nul,
            3,
            &group_members as *const _ as *const u8,
            1,
        )
    };

    if rc != 0 {
        anyhow::bail!("Failed to manage user group. Error {rc}.");
    }

    Ok(())
}

/// Convert &str into PWSTR (null terminated).
pub fn str_to_pwstr(s: &str) -> (PWSTR, Vec<u16>) {
    let mut encoded = s.encode_utf16().chain([0u16]).collect::<Vec<u16>>();
    (PWSTR(encoded.as_mut_ptr()), encoded)
}

Upvotes: 0

kmdreko
kmdreko

Reputation: 60472

You're sending dangling pointers.

Your winstr(...).as_mut_ptr() calls create a Vec<u16>, gets a pointer to its data, and drops the Vec<u16> since it was a temporary value. You need to keep those values at least until the call to NetUserAdd has finished:

let mut username = winstr("Test");
let mut password = winstr("Test******");
let mut user = USER_INFO_1{
    usri1_name: username.as_mut_ptr(),
    usri1_password: password.as_mut_ptr(),
    usri1_priv: 1,
    usri1_password_age: 0,
    usri1_home_dir: std::ptr::null_mut(),
    usri1_comment: std::ptr::null_mut(),
    usri1_flags: UF_SCRIPT,
    usri1_script_path: std::ptr::null_mut(),
};

Upvotes: 4

Related Questions