/* * libsam.c - SAM database functions, user and group editing * * Functions to edit SAM database, like adding and removing * users to groups, list users and groups * list user data and reset passwords * low level SID handling functions * * 2013-aug: Cleaned up a bit for release, still some debug/strange things left * 2013-aug: actually having functions doing listings in library is not good, bu * have to do with that for now. * 2013-apr-may: Functions for password reset, more group stuff etc * 2012-oct: Split off from functions in chntpw.c * 2012-jun-oct: Made routines for group handling (add/remove user from group etc) * * See HISTORY.txt for more detailed info on history. * ***** * * Copyright (c) 1997-2013 Petter Nordahl-Hagen. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * See file GPL.txt for the full license. * ***** * * Some information and ideas taken from pwdump by Jeremy Allison. * More info from NTCrack by Jonathan Wilkins. * */ #include #include #include #include #include "ntreg.h" #include "sam.h" extern int gverbose; /* Ehm.. must get rid of this some day */ /* Strings for account bits fields */ char *acb_fields[16] = { "Disabled" , "Homedir req." , "Passwd not req." , "Temp. duplicate" , "Normal account" , "NMS account" , "Domain trust act." , "Wks trust act." , "Srv trust act" , "Pwd don't expire" , "Auto lockout" , "(unknown 0x08)" , "(unknown 0x10)" , "(unknown 0x20)" , "(unknown 0x40)" , "(unknown 0x80)" , }; /* Number of paths we find group info under, needed by some later routines */ #define SAM_NUM_GROUPPATHS 2 /* Paths for group ID list*/ static char *SAM_GRPCPATHS[] = { "\\SAM\\Domains\\Builtin\\Aliases", "\\SAM\\Domains\\Account\\Aliases", "" }; /* Paths for C (group data) value under group ID %08X */ static char *SAM_GRPCPATHID[] = { "\\SAM\\Domains\\Builtin\\Aliases\\%08X\\C", "\\SAM\\Domains\\Account\\Aliases\\%08X\\C", "" }; /* Paths for users lists of group memberships, machine SID %s , user RID %08x * each key contains one default value (no name) with TYPE indicating number of group IDs it contains * value contents is then an array of 32 bit group IDs */ static char *SAM_GRPMEMBERSPATH[] = { "\\SAM\\Domains\\Builtin\\Aliases\\Members\\%s\\%08X", "\\SAM\\Domains\\Account\\Aliases\\Members\\%s\\%08X", "" }; static char *SAM_GRPSIDPATH[] = { "\\SAM\\Domains\\Builtin\\Aliases\\Members\\%s", "\\SAM\\Domains\\Account\\Aliases\\Members\\%s", "" }; /* Check if hive is SAM, and if it is, extract some * global policy information from it, like lockout counts etc * show = 1 means also print some more info * Returns the number of allowed logins before lockout (locklimit) * or -1 if error (not SAM, key not found etc) */ int sam_get_lockoutinfo(struct hive *hdesc, int show) { struct accountdb_F *f; struct keyval *v; if (hdesc->type == HTYPE_SAM) { /* Get accoundb F value */ v = get_val2buf(hdesc, NULL, 0, ACCOUNTDB_F_PATH, REG_BINARY, TPF_VK); if (!v) { fprintf(stderr,"WARNING: Login counts data not found in SAM\n"); return (-1); } f = (struct accountdb_F *)&v->data; if (show) { printf("\n* SAM policy limits:\n"); printf("Failed logins before lockout is: %d\n",f->locklimit); printf("Minimum password length : %d\n",f->minpwlen); printf("Password history count : %d\n",f->minpwlen); } return(f->locklimit); } return(-1); /* Not SAM */ } /* Try to decode and possibly change account lockout etc * This is \SAM\Domains\Account\Users\\F * It's size seems to always be 0x50. * Params: RID - user ID, mode - 0 silent, 1 print info, 2 edit. * Returns: ACB bits with high bit set if lockout count is >0 */ short sam_handle_accountbits(struct hive *hdesc, int rid, int mode) { struct user_F *f; char s[200]; struct keyval *v; unsigned short acb; int b; int max_sam_lock; if (hdesc->type != HTYPE_SAM) return(0); /* Get users F value */ snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\F",rid); v = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT); if (!v) { printf("Cannot find value <%s>\n",s); return(0); } if (v->len < 0x48) { printf("handle_F: F value is 0x%x bytes, need >= 0x48, unable to check account flags!\n",v->len); FREE(v); return(0); } max_sam_lock = sam_get_lockoutinfo(hdesc, 0); f = (struct user_F *)&v->data; acb = f->ACB_bits; if (mode == 1) { printf("Account bits: 0x%04x =\n",acb); for (b=0; b < 15; b++) { printf("[%s] %-15.15s | ", (acb & (1<failedcnt,max_sam_lock); printf("Total login count: %u\n",f->logins); } if (mode == 2) { /* MODE = 2, reset to default sane sets of bits and null failed login counter */ acb |= ACB_PWNOEXP; acb &= ~ACB_DISABLED; acb &= ~ACB_AUTOLOCK; f->ACB_bits = acb; f->failedcnt = 0; put_buf2val(hdesc, v, 0, s, REG_BINARY,TPF_VK_EXACT); /* TODO: Check error return */ printf("Unlocked!\n"); } return (acb | ( (f->failedcnt > 0 && f->failedcnt >= max_sam_lock)<<15 ) | (acb & ACB_AUTOLOCK)<<15 | (acb & ACB_DISABLED)<<15); } /***** SID data handling routines **********/ /* Get machines SID as binary (raw data) * str = pointer to buffer, first 20 bytes will be filled in * returns true if found, else 0 */ int sam_get_machine_sid(struct hive *hdesc, char *sidbuf) { struct accountdb_V *v; struct keyval *kv; uint32_t ofs; uint32_t len; /* Get accoundb V value */ kv = get_val2buf(hdesc, NULL, 0, ACCOUNTDB_V_PATH, REG_BINARY, TPF_VK); if (!kv) { fprintf(stderr,"sam_get_machine_sid: Machine SID not found in SAM\n"); return(0); } // hexdump(&(kv->data), 0, kv->len,1); v = (struct accountdb_V *)&kv->data; ofs = v->sid_ofs; len = v->sid_len + 4; ofs += 0x40; if (len != SID_BIN_LEN) { fprintf(stderr,"sam_get_machine_sid: WARNING: SID found, but it has len=%d instead of expected %d bytes\n",len,SID_BIN_LEN); } // printf("get_machine_sid: adjusted ofs = %x, len = %x (%d)\n",ofs,len,len); memcpy(sidbuf, (char *)v+ofs, len); // hexdump(sidbuf, 0, len, 1); return(1); } /* Make string out of SID, in S-1-5 authority (NT authority) * like S-1-5-21-516312364-151943033-2698651 * Will allocate return string (which can be of variable lenght) * NOTE: caller must free it * sidbuf = the SID binary data structure with it's type+counter first * * returns str: * 6 chars athority prefix (S-1-5-) * 4 * 10 digits (the 4 32 bit groups) * 3 for the - between the groups * 1 for null termination * 50 chars */ char *sam_sid_to_string(struct sid_binary *sidbuf) { int cnt, i; char *str = NULL; // hexdump(sidbuf, 0, 24, 1); if (sidbuf->revision != 1) { fprintf(stderr,"sam_sid_to_string: DEBUG: first byte unexpected: %d\n",sidbuf->revision); } cnt = sidbuf->sections; // printf("sid_to_string: DEBUG: sections = %d\n",cnt); str = str_dup("S-"); str = str_catf(str, "%u-%u", sidbuf->revision, sidbuf->authority); for (i = 0; i < cnt; i++) { str = str_catf(str,"-%u",sidbuf->array[i]); } // printf("sid_to_string: returning <%s>\n",str); return(str); } /* Stuff SID binary list into more easily handled arrays * sidbuf = binary list buffer (not changed, may point into value structure) * size = number of bytes of raw data * returns pointer to array, terminated with NULL pointer. * Keeps full binary data from each SID * All array space is allocated, call sam_free_sid_array() to free it. */ struct sid_array *sam_make_sid_array(struct sid_binary *sidbuf, int size) { int num = 0; int sidlen; struct sid_binary *sb; struct sid_array *array; CREATE(array, struct sid_array, 1); array[0].len = 0; array[0].sidptr = NULL; while (size > 0) { sidlen = sidbuf->sections * 4 + 8; // printf("make_sid_array: sidlen = %d\n",sidlen); ALLOC(sb, 1, sidlen); memcpy(sb, sidbuf, sidlen); array[num].len = sidlen; array[num].sidptr = sb; sidbuf = (void *)sidbuf + sidlen; size -= sidlen; num++; array = realloc(array, (num + 1) * sizeof(struct sid_array)); array[num].len = 0; array[num].sidptr = NULL; } return(array); } /* Free the sid array (from the function above) */ void sam_free_sid_array(struct sid_array *array) { int num = 0; while (array[num].sidptr) { free(array[num].sidptr); num++; } free(array); } /* Compare two SIDs, and return like strcmp */ int sam_sid_cmp(struct sid_binary *s1, struct sid_binary *s2) { int p; if (!s1 && !s2) return(0); if (!s1) return(-1); if (!s2) return(1); if (s1->sections < s2->sections) return(-1); /* s1 has shorter len, always smaller */ if (s1->sections > s2->sections) return(1); /* s1 has longer len, always larger */ /* Run compare since same length */ for (p = 0; p < s1->sections; p++) { if (s1->array[p] < s2->array[p]) return (-1); if (s1->array[p] > s2->array[p]) return (1); } /* At end. Thus equal */ return(0); } /************** GROUP DATA HANDLING ROUTINES ****************/ /* Get C value of a group ID, searching botg bult-in and user defined * hdesc - hive * grp - group ID * returns pointer to value buffer or NULL if not found */ struct keyval *sam_get_grpC(struct hive *hdesc, int grp) { struct keyval *c = NULL; int n = 0; char g[200]; /* Try built-in groups first (administrators, user, guests etc) */ while (*SAM_GRPCPATHID[n] && !c) { snprintf(g, 180, SAM_GRPCPATHID[n], grp); c = get_val2buf(hdesc, NULL, 0, g, 0, TPF_VK_EXACT); n++; } return(c); } /* Get list of group members for a group * Will get the SID list (as binary) into a buffer that will be allocated * according to the neccessary size (based on member count) * NOTE: Caller must free the buffer when not needed any more * grp = group ID * sidarray = pointer to pointer to sid array which will be allocated * Returns number of members in the group */ int sam_get_grp_members_sid(struct hive *hdesc, int grp, struct sid_array **sarray) { // char groupname[128]; struct sid_array *marray; struct keyval *c = NULL; struct group_C *cd; // int grpnamoffs, grpnamlen; int mofs, mlen; c = sam_get_grpC(hdesc, grp); if (c) { cd = (struct group_C *)&c->data; // grpnamoffs = cd->grpname_ofs + 0x34; // grpnamlen = cd->grpname_len; // cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen); // printf("get_grp_members_sid: group %x named %s has %d members\n",grp,groupname,cd->grp_members); mofs = cd->members_ofs; mlen = cd->members_len; // printf("get_grp_members_sid: mofs = %x, mlen = %x (%d)\n", mofs,mlen,mlen); // printf("get_grp_members_sid: ajusted: mofs = %x, mlen = %x (%d)\n", mofs + 0x34 ,mlen,mlen); // hexdump(&c->data, 0, c->len, 1); // hexdump(&cd->data[mofs], 0, mlen, 1); marray = sam_make_sid_array((struct sid_binary *)&cd->data[mofs], mlen); *sarray = marray; // sam_free_sid_array(marray); free(c); } else { printf("Group info for %x not found!\n",grp); *sarray = NULL; return(0); } return(cd->grp_members); } /* Put list of group members back into group C structure * grp = group ID * sidarray = pointer to sid array * Returns true if success */ int sam_put_grp_members_sid(struct hive *hdesc, int grp, struct sid_array *sarray) { char g[200]; char groupname[128]; struct keyval *c = NULL; struct group_C *cd; int grpnamoffs, grpnamlen; int mofs, mlen; int sidlen = 0; void *sidptr; int i, n; char *str; /* Try built-in groups first (administrators, user, guests etc) */ n = 0; while (*SAM_GRPCPATHID[n] && !c) { snprintf(g, 180, SAM_GRPCPATHID[n], grp); c = get_val2buf(hdesc, NULL, 0, g, 0, TPF_VK_EXACT); n++; } if (c) { cd = (struct group_C *)&c->data; grpnamoffs = cd->grpname_ofs + 0x34; grpnamlen = cd->grpname_len; cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen); if (gverbose) printf("put_grp_members_sid: group %x named %s has %d members\n",grp,groupname,cd->grp_members); mofs = cd->members_ofs; mlen = cd->members_len; if (gverbose) printf("put_grp_members_sid: ajusted: mofs = %x, mlen = %x (%d)\n", mofs + 0x34 ,mlen,mlen); if (gverbose) hexdump(&c->data, 0, c->len, 1); /* Get total size of new SID data */ for (i = 0; sarray[i].sidptr; i++) sidlen += sarray[i].len; if (gverbose) printf("put_grp_members_sid: new count : %d, new sidlen: %x\n",i,sidlen); /* Resize buffer with C structure */ c = realloc(c, 4 + mofs + sidlen + 0x34); /* offset of SIDs + sids lenght + pointer list at start */ c->len = 0x34 + mofs + sidlen; cd = (struct group_C *)&c->data; mofs = cd->members_ofs; sidptr = &cd->data[mofs]; for (i = 0; sarray[i].sidptr; i++) { if (gverbose) printf(" copying : %d len %x, at %x\n",i,sarray[i].len, sidptr); str = sam_sid_to_string(sarray[i].sidptr); if (gverbose) printf(" Member # %d = <%s>\n", i, str); FREE(str); memcpy(sidptr, sarray[i].sidptr, sarray[i].len); sidptr += sarray[i].len; } cd->members_len = sidlen; /* Update member count in C struct */ cd->grp_members = i; if (gverbose) hexdump(&c->data, 0, c->len, 1); if (!put_buf2val(hdesc, c, 0, g, 0, TPF_VK_EXACT)) { fprintf(stderr,"put_grp_members_sid: could not write back group info in value %s\n",g); free(c); return(0); } free(c); } else { printf("Group info for %x not found!\n",grp); return(0); } return(1); } /* Get group IDs a user is member of * rid = user ID * returns: since value data is just an array of grp ids (4 bytes each), * just return the keyval structure (size + data) * caller must free() keyval */ struct keyval *sam_get_user_grpids(struct hive *hdesc, int rid) { char s[200]; struct sid_binary sid; char *sidstr; int nk = 0; struct keyval *m = NULL; struct keyval *result = NULL; struct keyval *newresult = NULL; int count = 0; int size; int n; if (!rid || (hdesc->type != HTYPE_SAM)) return(NULL); if (!sam_get_machine_sid(hdesc, (char *)&sid)) { fprintf(stderr,"sam_get_user_grpids: Could not find machine SID\n"); return(0); } sidstr = sam_sid_to_string(&sid); /* Get member list for user on this machine */ n = 0; /* Look up user RID under computer SID under builtin and account path */ while (*SAM_GRPMEMBERSPATH[n]) { snprintf(s, 180, SAM_GRPMEMBERSPATH[n], sidstr, rid); if (gverbose) printf("sam_get_user_grpids: member path: %s\n",s); nk = trav_path(hdesc, 0, s, 0); if (nk) { /* Found a key */ /* Now, the TYPE field is the number of groups the user is member of */ /* Don't we just love the inconsistent use of fields!! */ nk += 4; count = get_val_type(hdesc,nk,"@",TPF_VK_EXACT); if (count == -1) { printf("sam_get_user_grpids: Cannot find default value <%s\\@>\n",s); n++; continue; } // printf("sam_get_user_grpids: User is member of %d groups:\n",count); /* This is the data size */ size = get_val_len(hdesc,nk,"@",TPF_VK_EXACT); /* It should be 4 bytes for each group */ // if (gverbose) printf("Data size %d bytes.\n",size); if (size != count * 4) { printf("sam_get_user_grpids: DEBUG: Size is not 4 * count! May not matter anyway. Continuing..\n"); } m = get_val2buf(hdesc, NULL, nk, "@", 0, TPF_VK_EXACT); if (!m) { printf("sam_get_user_grpids: Could not get value data! Giving up.\n"); FREE(sidstr); return(NULL); } /* At this point we have a value containing member list from this part of the tree */ /* Just append this one to the earlier ones */ newresult = reg_valcat(result, m); FREE(m); FREE(result); result = newresult; } n++; } FREE(sidstr); if (!result) { /* This probably means user is not in any group. Seems to be the case for a couple of XPs built in support / guest users. So just return */ if (gverbose) printf("sam_get_user_grpids: Cannot find RID under computer SID <%s>\n",s); return(NULL); } // printf(" sam_get_user_grpids done\n"); return(result); } /* Put/set group IDs a user is member of * rid = user ID * val = keyval structure of data, actual value data is a list * of ints, one per group * returns true if successful setting the value */ int sam_put_user_grpids(struct hive *hdesc, int rid, struct keyval *val) { char s[200]; char news[200]; char ks[12]; struct nk_key *newkey = NULL; struct sid_binary sid; char *sidstr; int n, grp, pnum; int newcount = 0; int nk = 0; int count = 0; struct keyvala *v; struct keyvala *new; struct keyvala entry; /* Pointers to value lists for each group path in use */ struct keyval *p[SAM_NUM_GROUPPATHS]; if (!rid || (hdesc->type != HTYPE_SAM)) return(0); if (!val) return(0); #if 0 if (!val->len) { printf("sam_put_user_grpids: zero list len\n"); // return(0); } #endif v = (struct keyvala *)val; if (!sam_get_machine_sid(hdesc, (char *)&sid)) { fprintf(stderr,"sam_put_user_grpids: Could not find machine SID\n"); return(0); } sidstr = sam_sid_to_string(&sid); for (n = 0; n < SAM_NUM_GROUPPATHS; n++) { ALLOC(p[n], sizeof(struct keyvala), 1); p[n]->len = 0; p[n]->data = 0; } /* Split value list into relevant stuff for each path */ for (n = 0; n < val->len >> 2; n++) { grp = v->data[n]; for (pnum = 0; pnum < SAM_NUM_GROUPPATHS; pnum++) { snprintf(s, 180, SAM_GRPCPATHID[pnum], grp); // printf("sam_put_user_grpids: split path: %s\n",s); nk = trav_path(hdesc, 0, s, TPF_VK_EXACT); /* Check if group is in path?? */ if (nk) { /* Yup, it is here */ entry.data[0] = grp; entry.len = 4; // printf("sam_put_user_grpids: path match for grp ID: %x\n", entry.data[1]); new = (struct keyvala *)reg_valcat( p[pnum], (struct keyval *)&entry); FREE( p[pnum] ); p[pnum] = (struct keyval *)new; } } } /* Now put the lists into the correct place */ for (n = 0; n < SAM_NUM_GROUPPATHS; n++) { /* Get member list for user on this machine */ snprintf(s,180,SAM_GRPMEMBERSPATH[n] ,sidstr, rid); // printf("sam_put_user_grpids: putting for path: %s\n",s); newcount = p[n]->len >> 2; // printf("--- list for that path has len: %d\n",p[n]->len); for (pnum = 0; pnum < p[n]->len >> 2; pnum++) { new = (struct keyvala *)p[n]; // printf("%d : %x\n", pnum, new->data[pnum]); } /* Find users member list under this path */ nk = trav_path(hdesc, 0, s, 0); if (!nk) { /* User is not in any group in this path, see if we need to create key */ if (gverbose) printf("sam_put_user_grpids: Cannot find path <%s>\n",s); if (!newcount) continue; /* Nothing to put there anyway, so just try next path */ snprintf(news,180,SAM_GRPSIDPATH[n] ,sidstr); // snprintf(ks, 180, "%08X", rid); // printf("sam_put_user_grpids: creating key <%s> on path <%s>\n",ks,news); nk = trav_path(hdesc, 0, news, 0); newkey = add_key(hdesc, nk+4, ks); if (!newkey) { fprintf(stderr,"sam_put_user_grpids: ERROR: creating group list key for RID <%08x> under path <%s>\n",rid,news); abort(); } nk = trav_path(hdesc, 0, s, 0); if (!add_value(hdesc, nk+4, "@", 0)) { fprintf(stderr,"sam_put_user_grpids: ERROR: creating group list default value for RID <%08x> under path <%s>\n",rid,news); abort(); } } nk += 4; /* Now, the TYPE field is the number of groups the user is member of */ count = get_val_type(hdesc, nk,"@", TPF_VK_EXACT); if (count == -1) { printf("sam_put_user_grpids: Cannot find value <%s\\@>\n",s); return(1); } if (gverbose) printf("sam_put_user_grpids: User was member of %d groups:\n",count); /* This is the data size */ /* It should be 4 bytes for each group */ if (gverbose) printf("Data size %d bytes.\n",p[n]->len); if (p[n]->len != newcount << 2) { printf("set_user_grpids: DEBUG: Size is not 4 * count! May not matter anyway. Continuing..\n"); } if (gverbose) printf("sam_put_user_grpids: User is NOW member of %d groups:\n",newcount); if (newcount == 0) { /* Seems windows removes the key and default subvalue when user not in any group */ // printf("sam_put_user_grpids: removing user reference for path %s\n",s); del_value(hdesc, nk, "@", TPF_VK_EXACT); nk = trav_path(hdesc, nk, "..", 0); snprintf(s,180, "%08X", rid); del_key(hdesc, nk + 4, s); } else { /* Stuff back list into default value */ set_val_type(hdesc, nk, "@", TPF_VK_EXACT, newcount); if (!put_buf2val(hdesc, p[n], nk, "@", 0, TPF_VK_EXACT) ) { printf("sam_put_user_grpids: Could not set reg value data!\n"); return(0); } } } /* for path loop */ FREE(sidstr); for (n = 0; n < SAM_NUM_GROUPPATHS; n++) { FREE(p[n]); } printf("sam_put_user_grpids: success exit\n"); return(1); } /********* GROUP / USER MANIPULATION ROUTINES **************/ /* Add user to a group * rid = user RID * grp = group ID * return true if success */ int sam_add_user_to_grp(struct hive *hdesc, int rid, int grp) { struct keyvala *usrgrplist, *newusrgrplist; struct sid_array *sarray, *narray; struct sid_binary *usid; struct sid_binary msid; int members, newmembers; char *str; int ugcnt; int o,n,hit,c; unsigned int *og, *ng; char s[200]; if (!rid || !grp || (hdesc->type !=HTYPE_SAM) ) return(0); snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid); if (!trav_path(hdesc, 0, s, TPF_VK_EXACT)) { fprintf(stderr,"sam_add_user_to_grp: user # %x not found!\n",rid); return(0); } /* Build user SID (add RID to machine SID) */ if (!sam_get_machine_sid(hdesc, (char *)&msid)) { fprintf(stderr,"sam_add_user_to_grp: Could not find machine SID\n"); return(0); } /* well, and hope that machine SID is always same size here too */ ALLOC(usid, sizeof(struct sid_binary) +4, 1); memcpy(usid, &msid, sizeof(struct sid_binary)); usid->array[4] = rid; /* Tack RID on at end */ usid->sections = 5; str = sam_sid_to_string(usid); if (gverbose) printf("add_user_to_grp: user SID is <%s>\n", str); free(str); /* With all of the above functions, it should now just be to get * the list of groups the user account has listed under it * and the list of users the group has listed under it */ usrgrplist = (struct keyvala *)sam_get_user_grpids(hdesc, rid); if (!usrgrplist) { printf("sam_add_user_to_grp: user # %x WAS IN NO GROUPS!\n",rid); /* So make new blank list for it */ ALLOC(usrgrplist, sizeof(struct keyvala), 1); usrgrplist->len = 0; usrgrplist->data[0] = 0; } members = sam_get_grp_members_sid(hdesc, grp, &sarray); if (!sarray) { printf("sam_add_user_to_grp: group # %x not found!\n",grp); FREE(usrgrplist); return(0); } /* Add the group to the users list of groups it is member of */ ugcnt = usrgrplist->len >> 2; /* Count of groups already on user */ /* Allocate new larger usrgrplist for one more entry */ ALLOC(newusrgrplist, usrgrplist->len + 4 + 4, 1); bzero(newusrgrplist, usrgrplist->len + 4 + 4); /* for sanity.. */ newusrgrplist->len = usrgrplist->len + 4; og = (unsigned int *)&usrgrplist->data; ng = (unsigned int *)&newusrgrplist->data; if (gverbose) printf("usrgrplist-len = %d\n", usrgrplist->len); #if 0 /* If list should be sorted, but seems windows does not do that? */ /* Copy over users group list, adding in where needed */ hit = 0; for (o = 0, n = 0; o < ugcnt; o++, n++) { printf(":: %d %d : %x\n",o,n,og[o]); if (og[o] == grp) { /* Was already in there, so just don't increase size.. */ newusrgrplist->len = usrgrplist->len; hit = 1; } if (og[o] > grp && !hit) { ng[n++] = grp; /* Next is higher, so insert out rid */ hit = 1; printf(" -- insert\n"); } ng[n] = og[o]; } printf("n = %d\n",n); if (!hit) ng[n] = grp; /* Insert at end if we run down */ #endif /* Copy over users group list, checking if already there */ hit = 0; for (o = 0; o < ugcnt; o++) { if (gverbose) printf(":: %d : %x\n",o,og[o]); if (og[o] == grp) { /* Was already in there, so just don't increase size.. */ newusrgrplist->len = usrgrplist->len; hit = 1; if (gverbose) printf(" -- match\n"); } ng[o] = og[o]; } if (gverbose) printf(" - end of list at o = %d\n",o); if (!hit) ng[o] = grp; /* Just stuff new group in at end if not already in list */ if (gverbose) { for (o = 0; o < (newusrgrplist->len >> 2); o++) { printf("grp index %d = %08x\n", o, ng[o]); } } /* And then we add the user SID into the groups list of members */ if (gverbose) { printf("add_user_to_grp: grp memberlist BEFORE:\n"); for (o = 0; sarray[o].sidptr; o++) { str = sam_sid_to_string(sarray[o].sidptr); printf(" Member # %d = <%s>\n", o, str); FREE(str); } } newmembers = members + 1; ALLOC(narray, sizeof(struct sid_array) * (newmembers + 2), 1); /* Add one entry size */ if (gverbose) printf("members = %d\n", members); hit = 0; for (o = 0, n = 0; o <= members; o++, n++) { c = sam_sid_cmp(sarray[o].sidptr, usid); /* Compare slot with new SID */ if (gverbose) printf("sam_sid_cmp returns %d\n",c); if (c == 0) { newmembers--; /* Already there, don't change anything */ hit = 1; } if (!hit && ((c > 0) || !sarray[o].sidptr)) { /* Next is higher, insert new SID */ if (gverbose) printf(" -- add\n"); narray[n].len = usid->sections * 4 + 8; /* Hmm */ narray[n].sidptr = usid; n++; hit = 1; } narray[n].len = sarray[o].len; narray[n].sidptr = sarray[o].sidptr; } if (gverbose) { printf("add_user_to_grp: grp memberlist AFTER:\n"); for (o = 0; narray[o].sidptr; o++) { str = sam_sid_to_string(narray[o].sidptr); printf(" Member # %d = <%s>\n", o, str); FREE(str); } } /* Write new lists back to registry */ if (!sam_put_user_grpids(hdesc, rid, (struct keyval *)newusrgrplist)) { fprintf(stderr, "add_user_to_grp: failed storing users group list\n"); } else if (!sam_put_grp_members_sid(hdesc, grp, narray)) { fprintf(stderr,"add_user_to_grp: failed storing groups user list\n"); sam_put_user_grpids(hdesc, rid, (struct keyval *)usrgrplist); /* Try to roll back */ } FREE(usrgrplist); FREE(newusrgrplist); sam_free_sid_array(narray); FREE(sarray); /* Pointers was copied to narray, and freed above, just free the array here */ return(1); } /* Remove user from a group * rid = user RID * grp = group ID * return true if success */ int sam_remove_user_from_grp(struct hive *hdesc, int rid, int grp) { struct keyvala *usrgrplist, *newusrgrplist; struct sid_array *sarray, *narray; struct sid_binary *usid; struct sid_binary msid; int members, newmembers; char *str; int ugcnt; int o,n,hit,c; unsigned int *og, *ng; if (!rid || !grp || (hdesc->type != HTYPE_SAM)) return(0); /* Build user SID (add RID to machine SID) */ if (!sam_get_machine_sid(hdesc, (char *)&msid)) { fprintf(stderr,"sam_remove_user_from_grp: Could not find machine SID\n"); return(0); } /* well, and hope that machine SID is always same size here too */ ALLOC(usid, sizeof(struct sid_binary) +4, 1); memcpy(usid, &msid, sizeof(struct sid_binary)); usid->array[4] = rid; /* Tack RID on at end */ usid->sections = 5; if (gverbose) { str = sam_sid_to_string(usid); printf("remove_user_from_grp: user SID is <%s>\n", str); free(str); } /* With all of the above functions, it should now just be to get * the list of groups the user account has listed under it * and the list of users the group has listed under it */ usrgrplist = (struct keyvala *)sam_get_user_grpids(hdesc, rid); if (!usrgrplist) { printf("remove_user_from_grp: user # %x not found!\n",rid); return(0); } members = sam_get_grp_members_sid(hdesc, grp, &sarray); if (!sarray) { printf("remove_user_from_grp: group # %x not found!\n",grp); FREE(usrgrplist); return(0); } /* Add the group to the users list of groups it is member of */ ugcnt = usrgrplist->len >> 2; /* Count of groups already on user */ /* Allocate same size usrgrplist, since we don't know if we are in there and need to be removed */ ALLOC(newusrgrplist, usrgrplist->len + 4, 1); bzero(newusrgrplist, usrgrplist->len + 4); /* for sanity.. */ newusrgrplist->len = usrgrplist->len; og = (unsigned int *)&usrgrplist->data; ng = (unsigned int *)&newusrgrplist->data; if (gverbose) printf("usrgrplist-len = %d\n", usrgrplist->len); /* Copy over users group list, if relevant group found, don't copy it over */ hit = 0; for (o = 0; o < ugcnt; o++) { if (gverbose) printf(":: %d : %x\n",o,og[o]); if (og[o] == grp) { /* Group found */ hit = 1; if (gverbose) printf(" -- match\n"); } else { ng[o-hit] = og[o]; } } if (gverbose) printf(" - end of list at o = %d\n",o); if (hit) { newusrgrplist->len -= 4; /* Decrease size if found */ } else { fprintf(stderr, "remove_user_from_grp: NOTE: group not in users list of groups, may mean user not member at all. Safe. Continuing.\n"); } if (gverbose) { for (o = 0; o < (newusrgrplist->len >> 2); o++) { printf("grp index %d = %08x\n", o, ng[o]); } /* Remove the user SID from the groups list of members */ printf("remove_user_from_grp: grp memberlist BEFORE:\n"); for (o = 0; sarray[o].sidptr; o++) { str = sam_sid_to_string(sarray[o].sidptr); printf(" Member # %d = <%s>\n", o, str); FREE(str); } } newmembers = members; ALLOC(narray, sizeof(struct sid_array) * (newmembers + 2), 1); if (gverbose) printf("members = %d\n", members); hit = 0; for (o = 0, n = 0; o <= members; o++, n++) { c = sam_sid_cmp(sarray[o].sidptr, usid); /* Compare slot with new SID */ if (gverbose) printf("sid_cmp returns %d\n",c); if (c == 0) { newmembers--; /* Found, skip copy and decrease list size */ hit = 1; n--; } else { narray[n].len = sarray[o].len; /* Copy entry */ narray[n].sidptr = sarray[o].sidptr; } } if (!hit) fprintf(stderr, "remove_user_from_grp: NOTE: user not in groups list of users, may mean user was not member at all. Does not matter, continuing.\n"); if (gverbose) { printf("remove_user_from_grp: grp memberlist AFTER:\n"); for (o = 0; narray[o].sidptr; o++) { str = sam_sid_to_string(narray[o].sidptr); printf(" Member # %d = <%s>\n", o, str); FREE(str); } } /* Write new lists back to registry */ if (!sam_put_user_grpids(hdesc, rid, (struct keyval *)newusrgrplist)) { fprintf(stderr, "remove_user_from_grp: failed storing users group list\n"); } else if (!sam_put_grp_members_sid(hdesc, grp, narray)) { fprintf(stderr,"remvoe_user_from_grp: failed storing groups user list\n"); sam_put_user_grpids(hdesc, rid, (struct keyval *)usrgrplist); /* Try to roll back */ } FREE(usrgrplist); FREE(newusrgrplist); sam_free_sid_array(narray); FREE(sarray); /* Pointers was copied to narray, and freed above, just free the array here */ return(1); } /* TODO: So.. having listing functions in library.. should better be handled by tools.. */ /* List users membership or check if admin (is in admin group) * rid - users rid * check - if 1 just check if admin, do not list * returns true if user is admin */ int sam_list_user_groups(struct hive *hdesc, int rid, int check) { char groupname[128]; struct keyval *m = NULL, *c = NULL; struct group_C *cd; unsigned int *grps; int count = 0, isadmin = 0; int i, grp, grpnamoffs, grpnamlen; if (!rid || (hdesc->type != HTYPE_SAM) ) return(0); m = sam_get_user_grpids(hdesc, rid); if (!m) return(0); grps = (unsigned int *)&m->data; count = m->len >> 2; for (i = 0; i < count; i++) { grp = grps[i]; if (!check) printf("%08x ",grp); if (grp == 0x220) isadmin = 1; if (!check) { c = sam_get_grpC(hdesc, grp); if (c) { cd = (struct group_C *)&c->data; grpnamoffs = cd->grpname_ofs + 0x34; grpnamlen = cd->grpname_len; cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen); printf("= %s (which has %d members)\n",groupname,cd->grp_members); // get_grp_members_sid(grp, &sidbuf); } else { printf("Group info for %x not found!\n",grp); } } } free(m); return(isadmin); } /* List users in SAM file * readable - 1 = list in human readable form, 0 = colon-separated, 2 = quiet, no ouput (find admin) * return logic: * If no users / error: 0 * If only 0x1f4 (built-in adminsitrator) is admin (or no one at all in admin group), return 0x1f4 * Else return lowest numbered user that is in admin group * * Fields ouput in parsable listing (all numbers in hex) * rid:username:isadmin:acb:hashlen * rid = User RID * isadmin = (boolean flag) 1 user is admin, 0 is not * acb = ACB account bits * hashlen = lenght of password hash, 14 is normal if has passwd, 4 if blank */ char SAMdaunPATH[] = "\\SAM\\Domains\\Account\\Users\\Names\\"; int sam_list_users(struct hive *hdesc, int readable) { char s[200]; struct keyval *v; int nkofs /* ,vkofs */ ; int rid; int count = 0, countri = 0; int ntpw_len; int admrid = 0x1f4; int isadm; unsigned short acb; struct user_V *vpwd; struct ex_data ex; if (hdesc->type != HTYPE_SAM) return(0); nkofs = trav_path(hdesc, 0, SAMdaunPATH, 0); if (!nkofs) { printf("sam_list_users: Cannot find usernames in registry! (is this a SAM-hive?)\n"); return(0); } if (readable == 1) printf("| RID -|---------- Username ------------| Admin? |- Lock? --|\n"); while ((ex_next_n(hdesc, nkofs+4, &count, &countri, &ex) > 0)) { /* Extract the value out of the username-key, value is RID */ snprintf(s,180,"%s%s\\@",SAMdaunPATH, ex.name); rid = get_dword(hdesc, 0, s, TPF_VK_EXACT|TPF_VK_SHORT); /* Now that we have the RID, build the path to, and get the V-value */ snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid); v = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT); if (!v) { printf("Cannot find value <%s>\n",s); return(1); } if (v->len < 0xcc) { printf("sam_list_users: Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n", s, v->len); } else { vpwd = (struct user_V *)&(v->data); ntpw_len = vpwd->ntpw_len; acb = sam_handle_accountbits(hdesc, rid, 0); isadm = sam_list_user_groups(hdesc, rid, 1); if (isadm && rid != 0x1f4) { /* Found an non-built-in administrator */ if (admrid == 0x1f4) admrid = rid; /* Prefer anything over built-in one */ if (rid < admrid) admrid = rid; } if (readable == 1) { printf("| %04x | %-30.30s | %-6s | %-8s |\n", rid, ex.name, ( isadm ? "ADMIN" : "") , ( acb & 0x8000 ? "dis/lock" : (ntpw_len < 16) ? "*BLANK*" : "") ); } else if (readable == 0) { printf("%04x:%s:%d:%x:%x\n", rid, ex.name, isadm , acb, ntpw_len ); } // change_pw( (char *)&v->data , rid, v->len, (*automode == 'l') ? 2 : 1); } FREE(v); FREE(ex.name); } return(admrid); } /* Get username when we have a RID * hdesc - hive * rid - just that.. :) * returns allocated string with username, caller must free it * or NULL if RID not found in local databse */ char *sam_get_username(struct hive *hdesc, int rid) { char s[200]; char *username = NULL; int username_offset,username_len; struct user_V *v; struct keyval *value; int vlen; char *vp; snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid); value = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT); if (!value) { printf(" sam_get_username: ERROR: User with RID 0x%x not found, path <%s>\n",rid,s); return(NULL); } vlen = value->len; if (vlen < 0xcc) { printf(" sam_get_username: Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n", s, vlen); FREE(value); return(NULL); } v = (struct user_V *)&value->data; vp = (char *)&value->data; username_offset = v->username_ofs; username_len = v->username_len; if(username_len <= 0 || username_len > vlen || username_offset <= 0 || username_offset >= vlen) { printf(" sam_get_username: Not a legal V struct? (negative struct lengths)\n"); FREE(value); return(0); } /* Offsets in top of struct is relative to end of pointers, adjust */ username_offset += 0xCC; ALLOC(username, 2, (username_len >> 1) + 4); *username = 0; cheap_uni2ascii(vp + username_offset,username,username_len); if (gverbose) { printf("RID : %04d [%04x]\n",rid,rid); printf("Username: %s\n",username); } FREE(value); return(username); } /* Get username from SID: * Local database if matching machine SID * Well known SIDs if in list * Else probably domain SID, don't know how to find more info * * sid = sid to extract RID from * * returns a string (which must be free()d) or NULL if not able to resolve */ char *sam_get_username_from_sid(struct hive *hdesc, struct sid_binary *sid) { int rid; char *str; int i; struct sid_binary msid; struct known_sidentry { int val; char *name; }; const struct known_sidentry ntauthority_table[] = { { 4, "INTERACTIVE" }, { 11, "Authenticated Users" }, { 17, "IUSR" }, { 20, "NETWORK SERVICE" }, { 0, "" } }; if (!sid) return(NULL); if(sid->sections < 1) return(NULL); rid = sid->array[sid->sections-1]; if (sid->sections != 5) { // fprintf(stderr," WARNING: sam_get_rid_from_sid: Strange size SID, sections = %d, not 5, got rid = %d\n",sid->sections, rid); if (sid->sections == 1) { if (sid->authority == 5) { /* We only handle S-1-5 (NTAUTHORITY) known names yet */ str = str_dup("NT AUTHORITY\\"); for (i = 0; ntauthority_table[i].val; i++) { if (rid == ntauthority_table[i].val) { str = str_cat(str, ntauthority_table[i].name); return(str); } } } else { /* Not NT AUTHORITY */ return(sam_sid_to_string(sid)); } } /* sections */ return(sam_sid_to_string(sid)); } if (sam_get_machine_sid(hdesc, (char *)&msid)) { sid->sections--; /* Don't compare RID part */ if (!sam_sid_cmp(sid, &msid)) { sid->sections++; return(sam_get_username(hdesc, rid)); /* Match, find and return local username */ } else { sid->sections++; return(sam_sid_to_string(sid)); /* No match with local machine SID, so just return SID string */ } } /* If we get here we don't have a machine SID, so, well, try to get a local name anyway */ return(sam_get_username(hdesc, rid)); } /* List groups in SAM * hdesc - the hive * listmembers - true = list groups members also, else just group id/name etc * human = human readable form (true) or parsable (false) * * Format of group list: * grpid:grpname:membercount * Format of membership list: * grpid:grpname:index:rid:username:usersid * where index is just the ordinal number in the groups membership list (starts at 0) * grpid and rid is in hex */ void sam_list_groups(struct hive *hdesc, int listmembers, int human) { struct ex_data ex; struct sid_array *sids = NULL; int nkofs; unsigned int grp; int count,countri; struct keyval *c = NULL; struct group_C *cd; int grpnamoffs, grpnamlen, i; char groupname[200]; char *str; char *username; int pnum = 0; if (hdesc->type != HTYPE_SAM) return; while (*SAM_GRPCPATHS[pnum]) { // printf(" -- grp C list path: %s\n",SAM_GRPCPATHS[pnum]); nkofs = trav_path(hdesc, 0, SAM_GRPCPATHS[pnum], 0); if (!nkofs) { printf(" list_groups: Cannot find group list in registry! (is this a SAM-hive?)\n"); return; } /* Pick up all subkeys here, they are local groups */ count = 0; countri = 0; while ((ex_next_n(hdesc, nkofs+4, &count, &countri, &ex) > 0)) { // printf("Group ID %s\n",ex.name); sscanf(ex.name,"%x",&grp); /* Groups keys have a C value, get it and pick up the name etc */ /* Some other keys also exists (Members, Names at least), but we skip them */ c = get_val2buf(hdesc, NULL, ex.nkoffs+4, "C", 0, TPF_VK_EXACT); if (c) { cd = (struct group_C *)&c->data; grpnamoffs = cd->grpname_ofs + 0x34; grpnamlen = cd->grpname_len; cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen); if (human) printf("=== Group #%4x : %s\n",grp,groupname); else if (!listmembers) printf("%x:%s:%d\n",grp,groupname,cd->grp_members); if (listmembers) { sam_get_grp_members_sid(hdesc, grp, &sids); for (i = 0; sids[i].sidptr; i++) { str = sam_sid_to_string(sids[i].sidptr); username = sam_get_username_from_sid(hdesc, sids[i].sidptr); if (human) printf(" %3d | %04x | %-31s | <%s>\n", i, sids[i].sidptr->array[sids[i].sidptr->sections-1], username, str); else printf("%x:%s:%d:%x:%s:%s\n", grp, groupname, i, sids[i].sidptr->array[sids[i].sidptr->sections-1], username, str); FREE(username); FREE(str); } sam_free_sid_array(sids); } } /* if c */ } pnum++; } /* path loop */ } /* Get groupname when we have a group ID * hdesc - hive * grpid - just that.. :) * returns allocated string with username, caller must free it * or NULL if RID not found in local databse */ char *sam_get_groupname(struct hive *hdesc, int grpid) { struct keyval *value = NULL; struct group_C *cd; int grpnamoffs; int grpnamlen; char *groupname = NULL; value = sam_get_grpC(hdesc, grpid); if (!value) { printf(" sam_get_groupname: ERROR: Group ID 0x%x not found\n",grpid); return(NULL); } /* Offsets in top of struct is relative to end of pointers, adjust */ cd = (struct group_C *)&value->data; grpnamoffs = cd->grpname_ofs + 0x34; grpnamlen = cd->grpname_len; ALLOC(groupname, 2, (grpnamlen >> 1) + 4); *groupname = 0; cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen); // printf("==== Group #%4x : %s\n",grpid,groupname); FREE(value); return(groupname); } /* Reset users password * hdesc - the HIVE :) * rid - the users RID * * Returns: 0 = OK, 1 = error (for use in exit()) */ int sam_reset_pw(struct hive *hdesc, int rid) { char *vp; static char username[128],fullname[128]; int username_offset,username_len; int fullname_offset,fullname_len; int ntpw_len,lmpw_len,ntpw_offs,lmpw_offs; int vlen; struct user_V *v; struct keyval *value; char s[200]; if (!hdesc || !rid) return(1); /* Now that we have the RID, build the path to, and get the V-value */ snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid); value = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT); if (!value) { printf(" sam_reset_pw: ERROR: User with RID 0x%x not found, path <%s>\n",rid,s); return(1); } vlen = value->len; if (vlen < 0xcc) { printf(" sam_reset_pw: Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n", s, vlen); return(1); } v = (struct user_V *)&value->data; vp = (char *)&value->data; username_offset = v->username_ofs; username_len = v->username_len; fullname_offset = v->fullname_ofs; fullname_len = v->fullname_len; lmpw_offs = v->lmpw_ofs; lmpw_len = v->lmpw_len; ntpw_offs = v->ntpw_ofs; ntpw_len = v->ntpw_len; if (gverbose) { printf(" lmpw_offs: 0x%x, lmpw_len: %d (0x%x)\n",lmpw_offs,lmpw_len,lmpw_len); printf(" ntpw_offs: 0x%x, ntpw_len: %d (0x%x)\n",ntpw_offs,ntpw_len,ntpw_len); } *username = 0; *fullname = 0; if(username_len <= 0 || username_len > vlen || username_offset <= 0 || username_offset >= vlen || fullname_len < 0 || fullname_len > vlen || lmpw_offs < 0 || lmpw_offs >= vlen) { printf(" sam_reset_pw: Not a legal V struct? (negative struct lengths)\n"); FREE(value); return(0); } /* Offsets in top of struct is relative to end of pointers, adjust */ username_offset += 0xCC; fullname_offset += 0xCC; ntpw_offs += 0xCC; lmpw_offs += 0xCC; cheap_uni2ascii(vp + username_offset,username,username_len); cheap_uni2ascii(vp + fullname_offset,fullname,fullname_len); if (gverbose) { printf("RID : %04d [%04x]\n",rid,rid); printf("Username: %s\n",username); printf("fullname: %s\n",fullname); } /* Setting hash lengths to zero seems to make NT think it is blank * However, we probably leak about 40 bytes since I am to lazy to adjust the rest * of the V structure. */ v->ntpw_len = 0; v->lmpw_len = 0; if (!(put_buf2val(hdesc, value, 0, s, REG_BINARY, TPF_VK_EXACT))) { printf(" reset_pw: Failed to write updated <%s> to registry! Password change not completed!\n",s); FREE(value); return(1); } if (gverbose) printf(" reset_pw: Password cleared for user %s\n",username); FREE(value); return(0); } /* Reset password of ALL admin users * hdesc - hive * list - if true, list some info about users processed */ int sam_reset_all_pw(struct hive *hdesc, int list) { char s[200]; struct keyval *v; int nkofs; int rid; int isadm; int count = 0; int countri = 0; int fail = 0; struct ex_data ex; if (hdesc->type != HTYPE_SAM) return(0); nkofs = trav_path(hdesc, 0, SAMdaunPATH, 0); if (!nkofs) { printf("sam_reset_all_pw: Cannot find usernames in registry! (is this a SAM-hive?)\n"); return(1); } while ((ex_next_n(hdesc, nkofs+4, &count, &countri, &ex) > 0)) { /* Extract the value out of the username-key, value is RID */ snprintf(s,180,"%s%s\\@",SAMdaunPATH, ex.name); rid = get_dword(hdesc, 0, s, TPF_VK_EXACT|TPF_VK_SHORT); /* Now that we have the RID, build the path to, and get the V-value */ snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid); v = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT); if (!v) { printf("sam_reset_all_pw: Cannot find value <%s>\n",s); return(1); } if (v->len < 0xcc) { printf("sam_reset_all_pw: Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n", s, v->len); } else { isadm = sam_list_user_groups(hdesc, rid, 1); if (isadm) { if (list) printf("Reset user :%04x:%s\n", rid, ex.name ); fail |= sam_reset_pw(hdesc, rid); } } FREE(v); FREE(ex.name); } return(fail); }