#include #include #include #include #include #include #include #include #include #include "../paths.h" #include "internal.h" #include "userconf.h" #include "userconf.m" #include #include #include #include "usercomng.h" #include "../modules/module_apis/useracct_api.h" #include #include #include using namespace std; /* #Specification: userconf / etc/passwd /etc/passwd is the user database. Its permission flags are always set to 0644 when rewritten. */ #define ETC_PTMP "/etc/ptmp" static USERCONF_HELP_FILE help_users ("users"); static USERCONF_HELP_FILE help_filter ("filter"); static const char *tbargs[]={"user","domain",NULL}; static MESSAGE_DEF msgnew ("newuser",tbargs); static MESSAGE_DEF msgdel ("deluser",tbargs); static MESSAGE_DEF msgpostdel ("postdeluser",tbargs); static MESSAGE_DEF msgchg ("chguser",tbargs); static int (*user_fct_del)(const USER *, const SHADOW *)=NULL; static int (*user_fct_add)(USER *, SHADOW *, bool)=NULL; static int (*user_fct_mod)(const USER *, const SHADOW *)=NULL; int (*users_fct_read)(USERS *, USRACC_FILTER *)=NULL; int (*users_fct_fill)(USERS *, const char *name, int uid); /* record some hooks for distribution dependant account management methods */ void users_sethook ( int (*_user_fct_del)(const USER *, const SHADOW *), int (*_user_fct_add)(USER *, SHADOW *, bool), int (*_user_fct_mod)(const USER *, const SHADOW *)) { user_fct_del = _user_fct_del; user_fct_add = _user_fct_add; user_fct_mod = _user_fct_mod; } static CONFIG_FILE f_passwd (ETC_PASSWD,help_users ,CONFIGF_MANAGED|CONFIGF_TMPLOCK ,"root","root",0644,subsys_useraccounts); class USERS_PRIVATE{ public: USER *nisentry; bool nis_at_end; // NIS entry at the end or beginning class SHADOWS *shadows; CONFIG_FILE *configf; SSTRING home; // base directory for users home directory int baseuid; // Starting point for UID allocation SSTRING domain; // Domain associated with these accounts // / or a virtual email domain SSTRING root; // root directory of the domain / or vhome/domain // générally int maxusers; // Max number of users in this domain (0 is unlimited) bool mayadd; // May add user accounts bool allread; // readusers(force==true) was called, so we have // all the records, no need to use the fill function map idx; /*~PROTOBEG~ USERS_PRIVATE */ public: USERS_PRIVATE (void); ~USERS_PRIVATE (void); /*~PROTOEND~ USERS_PRIVATE */ }; PUBLIC USERS_PRIVATE::USERS_PRIVATE() { nisentry = NULL; nis_at_end = true; shadows = NULL; configf = NULL; baseuid = 0; maxusers = 0; mayadd = true; allread = false; } PUBLIC USERS_PRIVATE::~USERS_PRIVATE() { delete shadows; delete nisentry; } PRIVATE void USERS::readusers(bool force) { /* #Specification: /etc/passwd / strategy /etc/passwd is read "by hand" instead of using getpwent() to avoid getting all those NIS entries. This is done when editing local user account. */ priv->nisentry = NULL; priv->nis_at_end = true; if (users_fct_read != NULL && priv->domain.cmp("/")==0){ // We postpone the read if we can read as need (fill) if (users_fct_fill == NULL || force){ users_fct_read (this,NULL); priv->allread = true; } }else{ FILE_CFG *fin = priv->configf->fopen ("r"); if (fin != NULL){ char line[1000]; while (fgets(line,sizeof(line)-1,fin)!=NULL){ strip_end (line); if (line[0] != '\0'){ USER *usr = new USER(line); if (strcmp(usr->getname(),"+")==0){ delete priv->nisentry; priv->nisentry = usr; if (getnb()==0) priv->nis_at_end = false; }else{ add (usr); } } } fclose (fin); } } priv->shadows = NULL; rstmodified(); } PRIVATE int USERS::fill (const char *name, int uid) { int ret = -1; if (!priv->allread && priv->domain.cmp("/")==0 && users_fct_fill != NULL){ ret = users_fct_fill (this,name,uid); } return ret; } PUBLIC USERS::USERS() { priv = new USERS_PRIVATE; priv->home.setfrom (policies_getdefhome()); priv->domain.setfrom ("/"); priv->root.setfrom ("/"); priv->baseuid = 1; priv->configf = &f_passwd; readusers(false); if (shadow_exist()) priv->shadows = new SHADOWS; } PUBLIC USERS::USERS( CONFIG_FILE &_file, CONFIG_FILE &_shadow, const char *_root, const char *_home, const char *_domain, int _baseuid) { priv = new USERS_PRIVATE; priv->root.setfrom (_root); priv->home.setfrom (_home); priv->domain.setfrom (_domain); priv->baseuid = _baseuid; priv->configf = &_file; readusers(false); if (_shadow.exist()) priv->shadows = new SHADOWS(_shadow); priv->maxusers = 0; } /* Make sure all accounts are read in memory. Needed by accountbatch. */ PUBLIC void USERS::readall() { if (getnb()==0 && priv->domain.cmp("/")==0 && users_fct_read != NULL){ readusers(true); } } /* Set the maximum users in this domain. 0 means no limit. */ PUBLIC void USERS::setmaxusers(int mx) { priv->maxusers = mx; } /* Tells if we are allowed to add user accounts */ PUBLIC void USERS::setmayadd (bool mayadd) { priv->mayadd = mayadd; } PUBLIC void USERS::remove_all() { priv->idx.clear(); ARRAY::remove_all(); } /* Forget the current users and reload from file This is used after an override function has been called since it probably have modified the configuration files */ PRIVATE void USERS::reload() { if (priv->domain.cmp("/")!= 0 || users_fct_read == NULL){ SHADOWS *tmp = priv->shadows; priv->shadows = NULL; remove_all(); readusers(false); priv->shadows = tmp; if (priv->shadows != NULL) priv->shadows->reload(); } } /* Does this USERS list has shadow password support */ PUBLIC int USERS::has_shadow () const { return priv->shadows != NULL; } PUBLIC void USERS::add (USER *u) { ARRAY::add (u); priv->idx[u->getname()] = u; } PUBLIC int USERS::getnb() const { return ARRAY::size(); } PUBLIC int USERS::size() const { return ARRAY::size(); } PUBLIC void USERS::neverdelete() { return ARRAY::neverdelete(); } /* Return the recommend standard HOME base directory for users. */ PUBLIC const char *USERS::getstdhome() const { return priv->home.get(); } /* Return the root of the user base (root of the domain) */ PUBLIC const char *USERS::getroot() const { return priv->root.get(); } /* The the domain we are editing (/ for the main domain) */ PUBLIC const char *USERS::getdomain() const { return priv->domain.get(); } /* Return the recommend standard HOME base directory for a member of a group */ PUBLIC const char *USERS::getstdhome(const char *group) const { const char *ret = priv->home.get(); const char *grouphome = group_gethomebase(group); if (grouphome != NULL) ret = grouphome; return ret; } /* This object will contain a copy (only pointers). It is used as a temporary holder for the normal object, allowing doing some sort. */ PRIVATE USERS::USERS(USERS *users) { priv = new USERS_PRIVATE; int n = users->getnb(); neverdelete(); for (int i=0; igetitem(i)); } PUBLIC USERS::~USERS() { delete priv; } /* Get one USER specification of the table or NULL */ PUBLIC USER *USERS::getitem(int no) { return (USER*)ARRAY::getitem(no); } /* Get one USER specification of the table or NULL from his login name */ PUBLIC USER *USERS::getitem(const char *name, USER *exclude) { USER *ret = NULL; for (int j=0; j<2; j++){ map::iterator it = priv->idx.find(name); if (it != priv->idx.end()){ USER *usr = it->second; if (usr != exclude){ ret = usr; } break; }else if (j==0){ if (fill (name,-1)==-1) break; } } return ret; } /* Get one USER specification of the table or NULL from his login name */ PUBLIC USER *USERS::getitem(const char *name) { return getitem (name,NULL); } /* Get one SHADOW specification of the table or NULL from his login name */ PUBLIC SHADOW *USERS::getshadow(const USER *usr) { SHADOW *ret = NULL; if (priv->shadows != NULL) ret = priv->shadows->getitem(usr->getname()); return ret; } PUBLIC void USERS::addshadow (SHADOW *shadow) { priv->shadows->add (shadow); } /* Get one USER specification of the table or NULL from his UID */ PUBLIC USER *USERS::getfromuid(int uid, USER *exclude) { USER *ret = NULL; if (uid != -1){ for (int j=0; j<2; j++){ bool skipped = false; int nbu = getnb(); for (int i=0; igetuid() == uid){ if (usr == exclude){ skipped = true; }else{ ret = usr; } break; } } if (skipped) break; if (ret == NULL && j==0) fill (NULL,uid); } } return ret; } /* Get one USER specification of the table or NULL from his UID */ PUBLIC USER *USERS::getfromuid(int uid) { return getfromuid(uid,NULL); } /* Get one unused User ID. */ PUBLIC int USERS::getnewuid() { /* #Specification: userconf / automatic allocaion of uid We multiply gid by 500. From there we search in all user id and allocate the first uid in the range. We don't allocate into holes (unused uid between used one) to avoid uid reuse (and a security hole). This has shown problematic. Now linuxconf allocated the a new UID as the next one to the one with the highest uid number. */ int base = priv->baseuid; int maxu = 65530; // Some special UID exist at the end of the range // such as nobody which is often 65535 int ret = base; int nbu = getnb(); for (int i=0; igetuid(); if (uid >= base && uid < maxu){ if (uid >= ret) ret = uid + 1; } } return ret; } /* Write the /etc/passwd file with proper locking */ PUBLIC int USERS::write(PRIVILEGE *privi) { int ret = -1; //sortbygid(); if (priv->configf != NULL){ FILE_CFG *fout = priv->configf->fopen (privi,"w"); if (fout != NULL){ int nbu = getnb(); if (priv->nisentry != NULL && !priv->nis_at_end){ priv->nisentry->write(fout); } for (int i=0; iwrite(fout); } if (priv->nisentry != NULL && priv->nis_at_end){ priv->nisentry->write(fout); } ret = priv->configf->fclose(fout); // ret = priv->configf->relink_tmp(); if (priv->shadows != NULL) priv->shadows->write(privi); } } return ret; } /* Write the user accounts only if override maintenance function are not in place, since these function do the modification themselves. If the user account override function are in place, this means that other processes have updated the passwd database, so our own copy is invalid. Instead of writing, we reload */ PUBLIC int USERS::writeif (PRIVILEGE *priv, USER *&usr) { int ret = -1; if (user_fct_mod != NULL && may_override()){ SSTRING id; char enabled =1; if (usr != NULL){ id.setfrom (usr->getname()); enabled = usr->enabled; } reload(); if (usr != NULL){ usr = getitem(id.get()); if (usr == NULL){ xconf_notice ("Programming error:\n" "userconf/users.cc:writeif usr==NULL"); }else{ usr->enabled = enabled; } } ret = 0; }else{ ret = write (priv); } return ret; } PUBLIC int USERS::writeif (PRIVILEGE *priv) { USER *pt = NULL; return writeif (priv,pt); } PUBLIC USRACC_FILTER::USRACC_FILTER() { gid = -1; from = 0; to = 2000000000; } PRIVATE MENU_STATUS USERS::setselectprefix( struct USRACC_FILTER &filter, const USER *like, bool may_add) { MENU_STATUS ret = MENU_ACCEPT; int nbsel = 0; int nbu = getnb(); for (int i=0; iis_like(like)) nbsel++; } int prefix_trig = linuxconf_getprefixtrig(); if ((priv->domain.cmp("/")==0 && users_fct_fill != NULL) || (prefix_trig != 0 && nbsel > prefix_trig)){ DIALOG dia; dia.newf_str (MSG_U(F_USERPREFIX,"Login Prefix"), filter.prefix); dia.newf_str (MSG_U(F_GECOSPREFIX,"Full name prefix"), filter.gecos); dia.newf_num(MSG_U(F_UID_FROM, "UID From"),filter.from); dia.newf_num(MSG_U(F_UID_TO, "UID To"), filter.to); SSTRING group; GROUPS groups; if (like == NULL){ GROUP *g = groups.getfromgid(filter.gid); if (g != NULL) group.setfrom (g->getname()); FIELD_COMBO *grpl = dia.newf_combo (MSG_R(F_GROUP),group); // grpl->addopt (""); groups.setcombo (grpl); } int buts = MENUBUT_CANCEL|MENUBUT_ACCEPT; if (may_add) buts |= MENUBUT_ADD; int nof = 0; while (true){ ret = dia.edit_form (MSG_U(T_USERPREFIX,"Filter control") ,MSG_U(I_USERPREFIX ,"The list of users is long, so you\n" "may want to filter it a bit by providing\n" "a prefix to search. An empty prefix means to show\n" "all users.") ,help_filter ,nof,buts); if (ret == MENU_ACCEPT){ if (group.is_num()){ filter.gid = group.getval(); }else if (group.is_filled()){ filter.gid = groups.getgid(group.get()); if (filter.gid == -1){ xconf_error (MSG_U(E_IVLDGROUP,"Group %s does not exist") ,group.get()); continue; } }else{ filter.gid = -1; } if (getnb()==0 && users_fct_read != NULL && priv->domain.cmp("/")==0){ users_fct_read (this,&filter); } break; }else{ break; } } } return ret; } static bool users_match ( const char *str, const char *prefix, int len) { bool ret = true; if (len > 0){ // When using LDAPconf, we process the * at tne end // If the user enter a *, we use match effectivly all accounts // beginning with prefix. // Without *, we match one account // In none LDAP mode, we do not care about the * since there // are generally very few accounts (from 10 to 2000). char tmp[strlen(prefix)+1]; strcpy (tmp,prefix); if (tmp[len-1] == '*'){ len--; tmp[len] = '\0'; } if (tmp[0] != '*'){ ret = strncasecmp(str,tmp,len)==0; }else{ ret = strstr (str,tmp+1) != NULL; } } return ret; } #define _TLMP_seledit struct _F_seledit{ #define _F_seledit_editone(x) void x editone (USER *usr) virtual _F_seledit_editone( )=0; #define _F_seledit_add(x) void x add () virtual _F_seledit_add( )=0; }; /* Select one user from the list. May return NULL if no valid selection was done (escape). See code. */ static void seledit ( _F_seledit &c, USERS &users, USER *like, // Used to select which user to pick. // the function USER::islike() is called for // each. bool may_add, // Set the delete and add button USRACC_FILTER &filter) { glocal USRACC_FILTER *filter = &filter; glocal _F_seledit *c = &c; glocal USERS *users = &users; glocal bool may_add = may_add; glocal USER *like = like; (MSG_U(T_USERACCT,"Users accounts") ,may_add ? MSG_U(I_CANEDIT,"You can edit, add, or delete users") : MSG_U(I_SELECT,"You must select one of those users") ,help_users); newf_head(MSG_U(H_USERS,"Account\tName\tUid\tGroup\tExpire")); sortable(); sortpolicy ("aanaa"); sethdispo ("llrll"); if (glocal.may_add) addwhat (MSG_R(I_TOADD)); GROUPS groups; int nbu = glocal.users->getnb(); int myfrom = (glocal.filter->from < 1) ? 1 : glocal.filter->from; int myto = (glocal.filter->to < myfrom) ? myfrom: glocal.filter->to; /* #Specification: userconf / user account / root bin ... Some special account are simply left out of the configuration menu. These account are never edited. They make the list larger for no reason. Also account with special shells are not shown. This include accounts like uucp and slip. Theu are show in a separate menu. The same functionnality is used to edit those accounts, but the edition is trigerred from different menus. */ int len_prefix = glocal.filter->prefix.getlen(); const char *prestr = glocal.filter->prefix.get(); int len_gecos = glocal.filter->gecos.getlen(); const char *gecos = glocal.filter->gecos.get(); int today = time(NULL)/(24*60*60); for (int i=0; igetitem(i); int uid = usr->getuid(); if (usr->is_like(glocal.like) && users_match(usr->getname(),prestr,len_prefix) && users_match(usr->getgecos(),gecos,len_gecos) && (glocal.filter->gid == -1 || glocal.filter->gid == usr->getgid()) && uid >= myfrom && uid <= myto){ int gid = usr->getgid(); GROUP *grp = groups.getfromgid(gid); char grpstr[100]; if (grp == NULL){ sprintf (grpstr,"%d",gid); }else{ strncpy (grpstr,grp->getname(),99); } char gecos[50]; strncpy (gecos,usr->getgecos(),49); gecos[49] = '\0'; SHADOW *sha = glocal.users->getshadow(usr); char expdate[30]; expdate[0] = '\0'; if (sha != NULL){ int disable = sha->getdisable(); if (disable > 0){ if (today > disable) setnexttagged(); time_t tim = disable*24*60*60+1; strftime (expdate,sizeof(expdate)-1,"%Y/%m/%d",gmtime(&tim)); } } new_menuitemf (usr->getname(),"%s\t%d\t%s\t%s" ,gecos,usr->getuid(),grpstr ,expdate); set_lookup(i); } } glocal.c->editone (glocal.users->getitem(no)); glocal.c->add(); } #if 0 /* Select one user from the list. May return NULL if no valid selection was done (escape). See code. */ PUBLIC USER *USERS::select( USER *like, // Used to select which user to pick. // the function USER::islike() is called for // each. bool may_add, // Set the delete and add button MENU_STATUS &code, DIALOG_RECORDS &dia, int &choice, // Will contain the selection. Not so useful // but help for the reentrancy of the list // (It reedit on the last item selected). struct USRACC_FILTER &filter) { USERS sorted(this); sorted.sortbyname(); return sorted.select_sorted(like,may_add,code,dia,choice,filter); } #endif PUBLIC int USERS::dodel ( USER *usr, USER_DELOPER oper, GROUPS &groups, PRIVILEGE *priv) { int ret = 0; net_introlog (NETINTRO_DELETEUSER); net_prtlog (NETLOG_VERB,MSG_U(I_DELETING,"Delete account %s (%s)\n") ,usr->getname(),usr->getgecos()); GROUP *g = groups.getfromgid(usr->getgid()); const char *group = g != NULL ? g->getname() : ""; if (oper == DELOPER_ARCHIVE){ ret = runcmd(policies_getarchivecmd(),usr,group,300); }else if (oper == DELOPER_DELETE){ ret = runcmd(policies_getdeletecmd(),usr,group,60); } if (ret == 0){ ret = rundeletecmd(usr,group); } if (ret == 0 && user_fct_del == NULL){ if (groups.delmember (usr->getname())){ groups.write (priv); } } return ret; } /* Check if distribution dependant methode may be used. They can't be used on virtual email domain and they can't be used in administration trees. Once these "linuxconf specifics" become widespread, this may change */ PUBLIC bool USERS::may_override () { return priv->domain.cmp("/")==0 && context_isroot(); } /* Return true if the distribution dependant mechanism is used to create user accounts */ PUBLIC bool USERS::has_add_override() { return user_fct_add != NULL && may_override(); } /* Use a distribution dependant method to update the user account database if available */ PRIVATE int USERS::override ( PRIVILEGE *priv, const USER *usr, int (*fct) (const USER *, const SHADOW *)) { int ret = -1; if (fct == NULL || ! may_override()){ ret = 0; }else if (perm_access(priv,MSG_U(P_USERDB ,"to maintain the user database"))){ ret = (*fct)(usr,getshadow(usr)); if (ret != 0){ net_showlastlog(); } } return ret; } /* Update a single user account using a distribution dependant strategy Return -1 if any error. The usr object may be invalidated by this function. Forget about it. */ PUBLIC void USERS::update (USER *usr) { int ret = override (NULL,usr,user_fct_mod); if (ret != -1) ret = writeif (NULL); } /* Use a distribution dependant method to create a user account database if available */ PRIVATE int USERS::override_add ( PRIVILEGE *priv, USER *usr, const char *group) { int ret = -1; if (user_fct_add == NULL || ! may_override()){ ret = 0; }else if (perm_access(priv,MSG_R(P_USERDB))){ bool createdir = group_homeneeded(group) && policies_createhome(); ret = (*user_fct_add)(usr,getshadow(usr),createdir); if (ret == -1){ xconf_error (MSG_U(E_FAILCREATE,"Can't create user account")); }else if(createdir){ usr->sethome (NULL,group,false,*this); chmod (usr->wrkdir.get(),group_getcreateperm(group)); } } return ret; } PRIVATE int USERS::docreate (USER *usr, PRIVILEGE *priv, const char *group) { net_introlog (NETINTRO_CREATEUSER); net_prtlog (NETLOG_VERB,MSG_U(I_CREATING ,"Creating user account %s (%s)\n") ,usr->getname(),usr->getgecos()); add (usr); return override_add(priv,usr,group); } /* Edit/Commit a single account This is where we will call the modules one day so that they create the various objects which participate to the user account dialog */ PRIVATE int USERS::editone_precut ( USER *usr, bool is_new, PRIVILEGE *privi, // Privilege required to manage those accounts // or NULL if only root can do this unsigned may_edit) // Which fields may be edited { // #Specbeg: USERACCT_COMNG / dialog setup /* The following code define the context for the co-manager and enable them. */ GROUPS groups; USERACCT_COMNGS comngs; comngs.set_bool ("is_new",is_new); comngs.set_int ("may_edit",may_edit); comngs.set_str ("domain",priv->domain.get()); comngs.set_str ("name",usr->getname()); { GROUP *g = groups.getfromgid (usr->getgid()); if (g != NULL) comngs.set_str ("group",g->getname()); } comngs.set_int ("categ",usr->getcateg()); // This triggers the REGISTER_USERACCT_COMNG objets comngs.getall ("user"); // #Specend: comngs.set_obj ("groups",&groups); comngs.set_obj ("users",this); comngs.set_obj ("user",usr); USER_DELOPER deloper = DELOPER_NONE; int code = usr->edit (*this,groups,is_new,privi,may_edit ,comngs,deloper,priv->domain.get()); if (code != -1){ GROUP *g = groups.getfromgid(usr->getgid()); const char *group = g != NULL ? g->getname() : ""; SSTRING id (usr->getname()); // We must take a copy // because the usr object may be deleted // later. const char *tbmessage[]={ id.get(),priv->domain.get(),NULL // NULL to leave room for the usr pointer }; if (code == 0){ if (is_new){ if (docreate(usr,privi,group) != -1 && writeif (privi,usr) != -1){ module_sendmessage (msgnew,2,tbmessage); if (usr->enabled){ char info[100]; snprintf (info,sizeof(info)-1,"password:%s" ,usr->getname()); html_setcutinfo (info); usr->editpass(getshadow(usr),true,may_override() ,priv->domain.get()); } } }else{ net_introlog (NETINTRO_MODIFYUSER); if (override (privi,usr,user_fct_mod)!=-1){ tbmessage[2] = (const char *)usr; module_sendmessage (msgchg,3,tbmessage); tbmessage[2] = NULL; } } }else if (code == 1){ if (is_new){ delete usr; }else if (dodel(usr,deloper,groups,privi)!=-1){ module_sendmessage (msgdel,2,tbmessage); override (privi,usr,user_fct_del); remove_del (usr); module_sendmessage (msgpostdel,2,tbmessage); }else{ if (dialog_yesno (MSG_U(E_NOTDEL,"Account not deleted") ,MSG_U(I_NOTDEL ,"User account was not deleted\n" "because the post-deletion command failed\n" "\n" "Do you want to see the log ?") ,help_nil) ==MENU_YES){ net_showlog(); } if (dialog_yesno(MSG_U(E_DELANYWAY,"Delete anyway") ,MSG_U(I_DELANYWAY ,"Would you like to delete this account\n" "even if the post-deletion command failed ?") ,help_nil)==MENU_YES){ override (privi,usr,user_fct_del); remove_del (usr); }else{ code = -1; } } } if (code != -1){ USER tmp(usr); // writeif may invalidate the usr object, take a copy writeif(privi); if (user_fct_mod == NULL) groups.write(); // The qedit.save must be done after the /etc/passwd is saved if (code == 0){ // if (priv->domain.cmp("/")==0) qedit.save(privi); if (is_new) runcreatecmd(&tmp,group); comngs.save (privi); }else if (code == 1){ // if (priv->domain.cmp("/")==0) qedit.deluser(privi); comngs.deluser(privi); } } } return code; } /* Edit/Commit a single account This is where we will call the modules one day so that they create the various objects which participate to the user account dialog */ PUBLIC int USERS::editone ( USER *usr, bool is_new, PRIVILEGE *privi, // Privilege required to manage those accounts // or NULL if only root can do this unsigned may_edit) // Which field may be edited { int ret = -1; /* #Specification: user account / privilege / set default When editing a user account, we call the function perm_setdefprivi to register the default privilege. This is done to help co-manager do there tasks withoug being forced to pass PRIVILEGE pointers around all the time. This is annoying as it does not allow a sub-process to drop privilege easily (just by setting the PRIVILEGE pointer to NULL for example). */ PRIVILEGE *old = perm_setdefprivi (privi); const char *info = html_getcutinfo(); if (is_new && info != NULL && strncmp(info,"password:",9)==0){ usr = getitem (info+9); // fprintf (stderr,"Taking the cut for user :%s: %p\n",info+9,usr); if (usr != NULL){ ret = usr->editpass(getshadow(usr),true,may_override() ,priv->domain.get()); if (ret != -1){ writeif(privi,usr); } } }else{ // fprintf (stderr,"Not taking the cut\n"); ret = editone_precut (usr,is_new,privi,may_edit); } perm_setdefprivi (old); return ret; } /* Add one new user Return -1 if the user was not added. */ PUBLIC int USERS::addone ( USER *special, const char *name, // Proposed login name const char *fullname, // gecos field suggested PRIVILEGE *privi, // Privilege required to manage those accounts // or NULL if only root can do this unsigned may_edit) // Which field may be edited { int ret = -1; /* Don't add the user if it would exceed the objects maxusers limit. Used for virtual domains with user limit (maxusers=0 means unlimited). */ if ((priv->maxusers != 0) && (getnb() >= priv->maxusers)) { xconf_error(MSG_U(E_MAXUSERS ,"Adding user would exceed the userlimit for this domain\n(%d>%d) - aborting.") ,getnb()+1,priv->maxusers); }else{ USER *user = new USER; user->setlike (special); user->setname (name); user->setgecos (fullname); ret = editone (user,true,privi,may_edit); } return ret; } PUBLIC void USERS::remove_del (USER *usr) { priv->idx.erase(usr->getname()); SHADOW *sha = getshadow(usr); if (sha != NULL) priv->shadows->remove_del (sha); ARRAY::remove_del (usr); } /* General edition (addition/deletion/correction) of /etc/passwd */ PUBLIC int USERS::edit( USER *special, // Template for user creation // and selection. PRIVILEGE *privi, // Privilege required to manage those accounts // or NULL if only root can do this unsigned may_edit) { glocal int ret = -1; if (perm_access (privi,MSG_U(P_USERACCOUTS,"To view and edit user accounts"))){ /* predefined, we normally need these values... */ USRACC_FILTER filter; MENU_STATUS selcod = setselectprefix(filter,special,priv->mayadd); if (selcod == MENU_ADD){ if (perm_access(privi,MSG_R(P_USERDB))){ addone (special,NULL,NULL,privi,may_edit); } }else if (selcod == MENU_ACCEPT){ glocal unsigned may_edit = may_edit; glocal USERS *users = this; glocal USER *special = special; glocal PRIVILEGE *privi = privi; (*this,special,priv->mayadd,filter); int status = glocal.users->editone(usr,false,glocal.privi,glocal.may_edit); if (status != -1){ glocal.ret = 0; } glocal.users->addone (glocal.special,NULL,NULL,glocal.privi,glocal.may_edit); #if 0 DIALOG_RECORDS dia; int choice = 0; while (1){ MENU_STATUS code; USER *usr = select (special,priv->mayadd,code,dia,choice,filter); if (code == MENU_ESCAPE || code == MENU_QUIT){ break; }else if (code == MENU_OK){ if (usr != NULL){ int status = editone(usr,false,privi,may_edit); if (status != -1){ ret = 0; } } }else if (perm_access(privi,MSG_R(P_USERDB))){ if (code == MENU_ADD){ addone (special,NULL,NULL,privi,may_edit); } } } #endif } } return glocal.ret; } static int cmpbyname (const ARRAY_OBJ *o1, const ARRAY_OBJ *o2) { USER *g1 = (USER*) o1; USER *g2 = (USER*) o2; return strcmp(g1->getname(),g2->getname()); } /* Sort the array of group by name */ PUBLIC void USERS::sortbyname() { sort (cmpbyname); } static int cmpbygid (const ARRAY_OBJ *o1, const ARRAY_OBJ *o2) { USER *g1 = (USER*) o1; USER *g2 = (USER*) o2; int ret = g1->getgid() - g2->getgid(); if (ret == 0){ ret = strcmp(g1->getname(),g2->getname()); } return ret; } /* Sort the array of group by gid, and name */ PUBLIC void USERS::sortbygid() { sort (cmpbygid); } /* General edition of users account and special account See USERS::edit() */ void users_edit( USER *special, // Template for a special account. // Or NULL. PRIVILEGE *priv, // Privilege required to manage those accounts // or NULL if only root can do this unsigned may_edit) // Which field may be edited (USRACCT_EDIT____ { USERS users; users.edit(special,priv,may_edit); } /* Add one new special user. Return -1 if the user was not created */ int users_addone( const char *name, const char *group, // Which group to use or NULL. const char *fullname) // gecos field suggested { int ret = -1; PRIVILEGE *priv = special_getpriv (group); if (perm_access(priv,MSG_R(P_USERDB))){ USER *special; if (special_init (group,special) != -1){ USERS users; ret = users.addone(special,name,fullname,priv ,USRACCT_EDITSHELL|USRACCT_EDITGROUP|USRACCT_EDITSUPGRP); } delete special; } return ret; } /* Return != 0 if a user account exist. */ int user_exist (const char *name) { return getpwnam(name)!=NULL; } /* Edit one user spec. If the user does not exist, ask if we want to create it. Return -1 if the account was not created */ int users_editone( const char *name, const char *group, // Group to use (or NULL) if the user // is created const char *fullname) // gecos field suggested when creating a new // account { int ret = -1; USERS users; PRIVILEGE *priv = special_getpriv (group); if (perm_access(priv,MSG_R(P_USERDB))){ USER *user = users.getitem(name); if (user == NULL){ char buf[300]; sprintf (buf,MSG_U(I_USERCREATE,"User account %s does not exist\n" "Do you want to create it"),name); if (dialog_yesno(MSG_U(Q_USERCREATE,"Account creation") ,buf,help_users)==MENU_YES){ ret = users_addone (name,group,fullname); } }else{ ret = users.editone(user,false,priv ,USRACCT_EDITSHELL|USRACCT_EDITGROUP|USRACCT_EDITSUPGRP); } } return ret; } PRIVATE int USERS::runcmd( const char *cmd, USER *usr, const char *group, int timeout) { int ret = 0; if (cmd != NULL && cmd[0] != '\0'){ char gecos_esc[1000]; usr->getgecos_esc (gecos_esc,sizeof(gecos_esc)-1); char buf[strlen(cmd)+10+PATH_MAX]; sprintf (buf,"%s --uid %s --name %s --basehome %s --home %s --domain %s --group \"%s\"" ,cmd,usr->getname(),gecos_esc ,getstdhome(group) ,usr->gethome()+1 ,priv->domain.get() ,group); net_resetnberr(); bool old = net_setshowmode(false); ret = netconf_system (timeout,buf); if (net_getnberr()>0) ret = -1; net_setshowmode(old); } return ret; } /* Run a user supplied post creation command */ PROTECTED int USERS::runcreatecmd(USER *usr, const char *group) { return runcmd(policies_getcreatecmd(),usr,group,60); } /* Run a user supplied pre deletion command */ PROTECTED int USERS::rundeletecmd(USER *usr, const char *group) { return runcmd(policies_getpostdeletecmd(),usr,group,60); } /* Add a user account, no question asked */ PUBLIC int USERS::addbatch ( const char *id, const char *group, const char *name, const char *shell, const char *home, const char *altgr, bool locked) // This account will be created locked { int ret = -1; USER *u = getitem(id); GROUPS groups; GROUP *g = groups.getitem(group); if (u != NULL){ fprintf (stderr,MSG_U(E_UEXIST,"User account %s already exists\n") ,id); }else if (g == NULL){ fprintf (stderr,MSG_U(E_GNOTEXIST,"Group %s does not exist\n") ,group); }else{ u = new USER; //u->setlike (special); u->setname (id); u->setgecos (name); u->setaltgrs (altgr); if (home != NULL && home[0] != '\0'){ u->wrkdir.setfrom (home); }else{ u->setdefhome (*this,group); } u->shell.setfrom (shell); u->uid = has_add_override() ? -1 : getnewuid(); u->gid = g->getgid(); SHADOW *shadow = NULL; if (has_shadow()){ shadow = new SHADOW (); shadow->name.setfrom (id); addshadow (shadow); } if (docreate (u,NULL,group) != -1 && writeif (NULL,u) != -1){ if (locked){ u->update_passwd (NULL,shadow,true,priv->domain.get()); writeif (NULL,u); } const char *tbmessage[]={ u->getname(),priv->domain.get() }; u->sethome (NULL,group,false,*this); module_sendmessage (msgnew,2,tbmessage); runcreatecmd (u,group); ret = 0; } } return ret; } /* Delete a user account, no question asked */ PUBLIC int USERS::delbatch (const char *id, USER_DELOPER oper) { int ret = -1; USER *u = getitem(id); if (u == NULL){ fprintf (stderr,MSG_U(E_UNOTEXIST,"User account %s does not exist\n") ,id); }else{ GROUPS groups; if (dodel (u,oper,groups,NULL)==0){ SSTRING name (u->getname()); override (NULL,u,user_fct_del); remove_del (u); ret = writeif(NULL); if (ret != -1){ const char *tbmessage[]={ name.get(),priv->domain.get() }; module_sendmessage (msgdel,2,tbmessage); } }else{ fprintf (stderr,MSG_U(E_PREDELFAIL ,"Pre-delete command failed for account %s\n") ,id); } } return ret; } /* Add one user account */ int users_add ( const char *id, const char *group, const char *name, const char *shell, const char *home, const char *altgr) { USERS users; return users.addbatch (id,group,name,shell,home,altgr,true); } /* Delete one user account */ int users_del (const char *id, USER_DELOPER oper) { USERS users; return users.delbatch (id,oper); } static void users_edituser (const char *v, bool setting) { const char *id = strrchr(v,'/'); if (id == NULL){ users_editone (v,NULL,NULL); }else{ SSTRING domain; domain.setfrom (v,(int)(id-v)); id++; USERACCT_API *tbapi[MAX_API_PROVIDERS]; int nb = useracct_apis_init ("userconf",tbapi); for (int i=0; ieditaccount(tbapi[i],domain.get(),id,setting)>0){ break; } } useracct_apis_end (tbapi,nb); } } static void users_list (SSTRINGS &tb) { const char *domain = getenv("VDOMAIN"); if (domain != NULL){ USERACCT_API *tbapi[MAX_API_PROVIDERS]; int nb = useracct_apis_init ("userconf",tbapi); for (int i=0; ilistaccounts(tbapi[i],domain,tb)>0){ break; } } useracct_apis_end (tbapi,nb); }else{ USERS users; for (int i=0; igetname())); } } } #include static PUBLISH_VARIABLES_MSG user_var_list1[]={ {"login",P_MSG_R(F_LOGIN)}, {"home",P_MSG_R(F_HOME)}, {"shell",P_MSG_R(F_SHELL)}, {"enable",P_MSG_R(I_ENABLED)}, {"gecos",P_MSG_R(F_FULLNAME)}, {"uid",P_MSG_R(F_UID)}, { NULL, NULL } }; static REGISTER_VARIABLES user_registry1("user","users",user_var_list1 ,NULL,users_edituser,users_list);