#include #include #include #include #include "../paths.h" #include "internal.h" #include "userconf.h" #include "userconf.m" #include #include #include #include #include using namespace std; #define ETC_GTMP "/etc/gtmp" static USERCONF_HELP_FILE help_groups("groups"); static CONFIG_FILE f_group (ETC_GROUP,help_groups ,CONFIGF_MANAGED|CONFIGF_TMPLOCK ,"root","root",0644,subsys_useraccounts); static const char *tbargs[]={"group",NULL}; static MESSAGE_DEF msgnew ("newgroup",tbargs); static MESSAGE_DEF msgdel ("delgroup",tbargs); static MESSAGE_DEF msgpostdel ("postdelgroup",tbargs); static MESSAGE_DEF msgchg ("chggroup",tbargs); int (*groups_fct_read)(GROUPS *)=NULL; static int (*group_fct_del)(GROUP *)=NULL; static int (*group_fct_add)(GROUP *)=NULL; static int (*group_fct_mod)(GROUP *)=NULL; /* record some hooks for distribution dependant account management methods */ void groups_sethook ( int (*_group_fct_del)(GROUP *), int (*_group_fct_add)(GROUP *), int (*_group_fct_mod)(GROUP *)) { group_fct_del = _group_fct_del; group_fct_add = _group_fct_add; group_fct_mod = _group_fct_mod; } struct GROUPS_PRIVATE { GROUP *nisentry; bool nis_at_end; // NIS entry at the end or beginning map idx; GROUPS_PRIVATE(){ nisentry = NULL; nis_at_end = true; } ~GROUPS_PRIVATE(){ delete nisentry; } }; PUBLIC GROUPS::GROUPS() { /* #Specification: /etc/group / strategy /etc/group is read "by hand" instead of using getpwent() to avoid getting all those NIS entries. This is done when editing local group account. */ priv = new GROUPS_PRIVATE; if (groups_fct_read != NULL){ groups_fct_read (this); }else{ FILE_CFG *fin = f_group.fopen ("r"); if (fin != NULL){ char *line = NULL; int len = 0; while (fgets_long (line,len,fin)!=NULL){ strip_end (line); if (line[0] != '\0'){ GROUP *ng = new GROUP(line); if (strcmp(ng->getname(),"+")==0){ delete priv->nisentry; priv->nisentry = ng; if (getnb()==0) priv->nis_at_end = false; }else{ add (ng); } } } free (line); fclose (fin); } } rstmodified(); } /* This object will contain a copy (only pointers). It is used as a temporary holder for the normal object, allowing doing some sort. */ PRIVATE GROUPS::GROUPS(GROUPS *groups) { priv = new GROUPS_PRIVATE; int n = groups->getnb(); neverdelete(); for (int i=0; igetitem(i)); } PUBLIC GROUPS::~GROUPS() { delete priv; } PUBLIC void GROUPS::rstmodified() { return ARRAY::rstmodified(); } PUBLIC int GROUPS::getnb() const { return ARRAY::size(); } PUBLIC int GROUPS::size() const { return ARRAY::size(); } PUBLIC void GROUPS::add(GROUP *g) { ARRAY::add(g); priv->idx[g->getname()] = g; } /* Get one GROUP specification of the table or NULL */ PUBLIC GROUP *GROUPS::getitem(int no) const { return (GROUP*)ARRAY::getitem(no); } /* Return the GROUP specification from its name. Return NULL if not found. */ PUBLIC GROUP *GROUPS::getitem(const char *name) const { GROUP *ret = NULL; map::iterator it = priv->idx.find(name); if (it != priv->idx.end()){ ret = it->second; } return ret; } /* Return the gid of a group from its name. Return -1 if there is no group with this name. */ PUBLIC int GROUPS::getgid(const char *name) const { GROUP *grp = getitem(name); return grp == NULL ? -1 : grp->getgid(); } /* Write the /etc/group file with proper locking */ PUBLIC int GROUPS::write(PRIVILEGE *privi, CONFIG_FILE &file) { int ret = -1; load_special(); FILE_CFG *fout = file.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 = f_group.fclose(fout); } return ret; } /* Write the /etc/group file with proper locking */ PUBLIC int GROUPS::write(PRIVILEGE *priv) { return write (priv,f_group); } /* Write the /etc/group file with proper locking */ PUBLIC int GROUPS::write() { return write (NULL); } /* Return the name of the group to use when creating new user (default) */ PUBLIC const char *GROUPS::getdefault() const { /* #Specification: userconf / groups / default for creation The default group is "users". If this group does not exist. then the group "group" is used. If none of those group exist, then no default group is proposed to the user. */ /* #todo: userconf / groups and users / default for creation It is not clear if a default setup should exist such as /etc/default/useradd and could be edited by userconf. Currently, we assumed that the default user group is 1. */ const char *ret = NULL; if (getitem("users")!=NULL){ ret = "users"; }else if (getitem("group")!=NULL){ ret = "group"; } return ret; } /* Get one GROUP specification of the table or NULL from his GID */ PUBLIC GROUP *GROUPS::getfromgid(int gid) const { GROUP *ret = NULL; int nbg = getnb(); for (int i=0; igetgid() == gid){ ret = grp; break; } } return ret; } /* Remove one user as a member of a all groups (if there) This is called when a user account is deleted. Return != 0 if one group was modified (The user was member of at least one group). */ PUBLIC int GROUPS::delmember(const char *user) { int ret = 0; int n = getnb(); for (int i=0; idelmember (user) > 0){ ret |= 1; } } return ret; } /* Collect the alternate group for a user account They are simply append to alt with a space between them */ PUBLIC void GROUPS::getalt(const char *user, SSTRING &alt) { int n = getnb(); alt.setfrom (""); for (int i=0; iis_member (user)){ if (!alt.is_empty()) alt.append (" "); alt.append (grp->getname()); } } } /* Register a user in several groups. Return -1 if one group does not exist */ PUBLIC int GROUPS::setalt ( const char *user, const char *groups, char delim, // Separator in groups, either comma or a space bool test) // Only check if all groups exist { int ret = 0; SSTRINGS tb; int n = str_splitline (groups,delim,tb); for (int i=0; iget(); GROUP *grp = getitem(g); if (grp == NULL){ ret = -1; break; } } if (ret == 0 && !test){ delmember (user); for (int j=0; jget(); GROUP *grp = getitem(g); grp->addmember (user); } } return ret; } static int cmpbyid (const ARRAY_OBJ *o1, const ARRAY_OBJ *o2) { GROUP *g1 = (GROUP*) o1; GROUP *g2 = (GROUP*) o2; return g1->getgid() - g2->getgid(); } /* Sort the array of group by name */ PUBLIC void GROUPS::sortbyid() { sort (cmpbyid); } /* Allocate an unused group ID. */ PUBLIC int GROUPS::getnew(int hint) { /* #Specification: userconf / groups / group ID allocation When creating a new group, the group ID may be left blank. An unused group ID will be allocated. The first one unused will be allocated. We assume that a maximum of 65000 group may be configured. */ // We sort a temporary object to avoir changing the original // order of the file GROUPS tmp (this); tmp.sortbyid(); // If over bounds, set hint to 1 if (hint < 0 || hint > 65000) hint = 1; int nbg = getnb(); int next = hint; int ret = -1; for (int i=0; igetgid(); if (gid >= hint){ if (gid > next){ ret = next; break; }else{ next = gid + 1; } } } if (ret == -1) ret = next; if ( ret >= 65000 || ret < 1){ fprintf (stderr,"Out of bounds Bounds: gid: %d\n", ret); ret = -1; } return ret; } /* Allocate an unused group ID over 500 */ PUBLIC int GROUPS::getnew() { return getnew(500); } static void groups_sendmessage (const MESSAGE_DEF &msg, const char *group) { const char *tbmessage[]={ group,NULL }; module_sendmessage (msg,1,tbmessage); } static void groups_addwrite (GROUPS &groups, GROUP *grp) { groups.add (grp); if (group_fct_add != NULL){ group_fct_add (grp); }else{ groups.write(); } groups_sendmessage (msgnew,grp->getname()); groups.rstmodified(); } PUBLIC int GROUPS::create ( const char *name, int hint) // Try to create the group using this GID // Allocate another one if this fail { int gid = hint != -1 && getfromgid(hint)==NULL ? hint : getnew(); GROUP *grp = new GROUP (name,"*",gid,NULL); groups_addwrite (*this,grp); return gid; } static int cmpbyname (const ARRAY_OBJ *o1, const ARRAY_OBJ *o2) { GROUP *g1 = (GROUP*) o1; GROUP *g2 = (GROUP*) o2; return strcmp(g1->getname(),g2->getname()); } /* Sort the array of group by name */ PUBLIC void GROUPS::sortbyname() { sort (cmpbyname); } /* Sets the help list of a FIELD_COMBO with all the groups */ PUBLIC void GROUPS::setcombo (FIELD_COMBO *grpl) { GROUPS grs (this); grs.sortbyname(); for (int i=0; iaddopt (grs.getitem(i)->getname()); } } PUBLIC void GROUPS::remove_all () { priv->idx.clear(); ARRAY::remove_all(); } PUBLIC void GROUPS::remove_del (GROUP *grp) { priv->idx.erase(grp->getname()); ARRAY::remove_del (grp); } static void groups_delwrite (GROUPS &groups, GROUP *grp) { SSTRING groupname(grp->getname()); const char *name = groupname.get(); groups_sendmessage (msgdel,name); if (group_fct_del != NULL){ group_fct_del (grp); groups.remove_del(grp); }else{ groups.remove_del(grp); groups.write(); } groups_sendmessage (msgpostdel,name); groups.rstmodified(); } /* General edition (addition/deletion/correction) of /etc/group */ PUBLIC int GROUPS::edit() { glocal USERS users; glocal GROUPS *groups = this; glocal int ret = -1; (MSG_U(T_GROUPS,"User groups") ,MSG_U(I_GROUPS,"You can edit, add, or delete groups") ,help_groups); newf_head (MSG_U(H_GROUPS,"Group\tID\tAlternate members")); addwhat (MSG_U(I_TOADD,"Select [Add] to add a new definition")); sortable(); sortpolicy("ana"); sethdispo ("lrl"); int nbg = glocal.groups->getnb(); for (int i=0; igetitem(i); char buf[100]; unsigned len = sprintf (buf,"%d\t",g->getgid()); const SSTRINGS *tbmem = g->getmembers(); int nbmem = tbmem->size(); // We show the first 3 and put ... if there are more for (int m=0; mgetitem(m)->get(); unsigned lenu = strlen(u); if (len + lenu + 2 >= sizeof(buf)) break; len += sprintf (buf+len,"%s ",u); } new_menuitemf(g->getname(),"%s",buf); } GROUP *grp = glocal.groups->getitem(no); int status = grp->edit(glocal.users,*glocal.groups); if (status != -1){ if (status == 1){ groups_delwrite (*glocal.groups,grp); }else{ if (group_fct_mod != NULL){ group_fct_mod (grp); }else{ glocal.groups->write(); } groups_sendmessage (msgchg,grp->getname()); } glocal.ret = 0; } if (perm_rootaccess(MSG_R(P_GROUPDB))){ GROUP *ngrp = new GROUP; if (ngrp->edit(glocal.users,*glocal.groups)==0){ groups_addwrite (*glocal.groups,ngrp); glocal.ret = 0; }else{ delete ngrp; } } return glocal.ret; } void groups_edit() { GROUPS groups; groups.edit(); } /* Get the ID of a group. Create it if it does not exist. Return -1 if it was not created. */ int group_getcreate (const char *name, const char *reason) { GROUPS groups; int gid = groups.getgid (name); if (gid == -1){ char buf[1000]; sprintf (buf ,MSG_U(I_GROUPCREATE ,"The group %s does not exist on your\n" "system. It is needed to %s\n" "\n" "Do you want to create it ? (why not)") ,name,reason); if (dialog_yesno(MSG_U(Q_GROUPCREATE,"Mandatory group creation") ,buf,help_nil) == MENU_YES){ gid = groups.getnew(); GROUP *grp = new GROUP (name,"*",gid,NULL); groups_addwrite (groups,grp); } } return gid; } static int gid_pop=-2; static int gid_ppp; static int gid_uucp; static int gid_slip; PUBLIC void GROUPS::load_special() { gid_pop = getgid (POP_GROUP); gid_ppp = getgid (PPP_GROUP); gid_uucp = getgid (UUCP_GROUP); gid_slip = getgid (SLIP_GROUP); } static void groups_locatespecial() { if (gid_pop == -2){ GROUPS grps; grps.load_special(); } } /* Return the group ID of POP accounts or -1 if this group does not exist */ int groups_getpop() { groups_locatespecial(); return gid_pop; } /* Return the group ID of PPP accounts or -1 if this group does not exist */ int groups_getppp() { groups_locatespecial(); return gid_ppp; } /* Return the group ID of SLIP accounts or -1 if this group does not exist */ int groups_getslip() { groups_locatespecial(); return gid_slip; } /* Return the group ID of UUCP accounts or -1 if this group does not exist */ int groups_getuucp() { groups_locatespecial(); return gid_uucp; } /* Add one group, no questions asked. Calls getnew( ) to get an empty gid over 500. Added 3/98, Scimitar. */ int groups_add ( const char *name ) { GROUPS groups; int gid = groups.getgid(name); if (gid == -1){ gid = groups.getnew(); if (gid != -1){ GROUP *grp = new GROUP (name, "*",gid,NULL); groups_addwrite (groups,grp); } }else{ fprintf (stderr,MSG_U(E_GEXIST,"Group %s already exists\n"),name); return -1; } return gid; } /* Del one group, no questions asked. Added 3/98, Scimitar. */ int groups_del ( const char *name ) { GROUPS groups; GROUP *grp = groups.getitem( name ); if ( grp != NULL){ // if group found, delete it groups_delwrite(groups,grp); }else{ fprintf (stderr,MSG_R(E_GNOTEXIST),name); return -1; } return 1; } #ifdef TEST int main (int argc, char *argv[]) { groups_edit(); } #endif