/*************************************************************************/ /* LDAPCONF - Linuxconf module for LDAP operation. Copyright (C) 1999,2000,2001 Stein Vråle 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. **************************************************************************/ /* USERADMIN.cc These dialogs are mainly designed to be used with account type directories (like NIS/POSIX accounts). **************************************************************************/ #include #include #include #include #include #include "ldapconf_defs.h" #include "fields.h" static const char K_LASTID[]="lastid"; /* USERADMIN usermenu */ void ldap_usermenu(const char *profile) { const char *key_search = MSG_U(M_LDAP_PROFILE_SEARCH,"Search"); const char *key_add = MSG_U(M_LDAP_PROFILE_ADDUSER,"Adduser"); const char *key_list = MSG_U(M_LDAP_PROFILE_LIST,"Users"); const char *key_groups = MSG_U(M_LDAP_PROFILE_GROUP,"Groups"); DIALOG_MENU dia; int nof = 0; const char *key; dia.new_menuinfo ("",profile); dia.new_menuitem("",key_list); dia.new_menuitem("",key_groups); dia.new_menuitem("",key_search); dia.new_menuitem("",key_add); while (1){ MENU_STATUS code = dia.editmenu (profile // MSG_U(T_PROFILE_USE,"Directory manager") ,MSG_U(I_PROFILE_USE,"Directory manager") ,help_formclient ,nof,0); key = dia.getmenustr(nof); if (code == MENU_ESCAPE || code == MENU_QUIT){ break; }else if (key == key_search){ ldap_form_search(profile); }else if (key == key_add){ ldap_form_adduser(profile,NULL); }else if (key == key_list){ ldap_userlist(profile); }else if (key == key_groups){ ldap_groupadmin(profile); } } } /* Locate an attribute given its key in a SSTRING table expect a SSTRING to contain var: =value Return a pointer starting at value or NULL. */ const char *getldapattribute (const SSTRINGS &atlist,const char *key) { if (key==NULL) return NULL; //something is wrong, we're not going to find the attribute this way ! int lenkey = strlen(key); const char *ret = NULL; int n = atlist.getnb(); D(debugf(5,"getldapattribute n=%d, key=%s lenkey=%d\n",n,key,lenkey)); for (int i=0; iget(); D(debugf(7,"getldapattribute n=%d, key=%s lenkey=%d, trying match with %s\n",n,key,lenkey,pt)); pt = str_skip(pt); if (strncasecmp(pt,key,lenkey)==0 && pt[lenkey] == ':'){ // 2.0 ret = pt+lenkey+2; D(debugf(6,"getldapattribute FOUND ! returning : %s\n",ret)); break; } } if (!ret) D(debugf(6,"getldapattribute attribute not found ! returning NULL (key=%s)",key)); return ret; } /*! LDAPCLIENT edituser */ void ldap_form_edituser(const char *profile_name,const char *user_name) { SSTRING form_key; LDAPOBJECT ldap(profile_name); CONFDB ldapdb; int nof = 0; form_key.setfrom(user_name); FIELD_DEFS defs(ldap.form); DIALOG dia; dia.newf_info ("Directory",profile_name); dia.newf_info (MSG_U(I_FORM_DATA,"Form"),ldap.formname.get()); dia.newf_info (MSG_U(I_FORM_KEY,"User"),form_key.get()); /* Extra buttons */ dia.setbutinfo (MENU_USR1,MSG_U(B_PASSWORD,"Passwd"),MSG_U(X_PASSWORD,"Passwd")); /* Read form fields */ defs.read(); /* Lookup field data */ ldap.search_base.setfromf("%s,%s",ldap.dn_prefix.get(),ldap.bind.base.get()); ldap.filter.setfromf("%s=%s",ldap.primary_key.get(),user_name); ldap.search(); ldap.export_confdb(ldapdb); /* Load field data */ defs.loadval (&ldapdb,form_key.get()); /* Draw form */ defs.setupdia(dia); while (1) { dia.reload(); MENU_STATUS code = dia.edit (MSG_U(T_FORM_CLIENT,"User") ,MSG_U(I_FORM_CLIENT,"This form will update userdata in the LDAP directory") ,help_formclient ,nof ,MENUBUT_USR1|MENUBUT_ACCEPT|MENUBUT_QUIT|MENUBUT_DEL); if (code == MENU_CANCEL || code == MENU_QUIT || code == MENU_ESCAPE){ /* Exit */ break; } else if (code == MENU_ACCEPT){ /* Update */ /* Check if this entry exist */ ldap.filter.setfromf("%s=%s",ldap.primary_key.get(),form_key.get()); int n = ldap.search(); if ( n == 1) { defs.saveval (ldapdb,form_key.get()); ldap.import_confdb(ldapdb,true); ldap.modify(); logf(LOG_NOTICE,"Updating user %s using profile %s",user_name,profile_name); break; } } else if (code == MENU_DEL){ /* Delete */ if (dialog_yesno("Delete user","Delete user from directory?",help_formclient) == MENU_YES) { ldap.del(); logf(LOG_NOTICE,"Deleting user %s using profile %s",user_name,profile_name); break; } } else if (code == MENU_USR1){ ldap_form_password(form_key.get(),profile_name); } } } /* LDAPCLIENT adduser */ void ldap_form_adduser(const char *profile_name,const char *user_name) { SSTRING user; // for example pdupond SSTRING password; SSTRING firstname; // optional example Patrick SSTRING lastname; // optional example Dupond SSTRING firstpluslast; // computed from fistname+lastname LDAPOBJECT ldap(profile_name); int nof; FIELD_DEFS defs(ldap.form); //to get default value defs.read(); // fill the structure with values DIALOG dia; dia.newf_info (MSG_U(I_PROFILE,"Profile"),profile_name); dia.newf_info (MSG_R(I_FORM_DATA),ldap.formname.get()); dia.newf_str (MSG_R(F_USERNAME),user); dia.newf_str (MSG_R(F_PASSWORD),password); dia.newf_title (MSG_U(I_OPTIONAL,"Optional"),1,"",MSG_R(I_OPTIONAL)); dia.newf_str (MSG_R(F_GIVENNAME),firstname); dia.newf_str (MSG_R(F_SN),lastname); //! \todo add here a list with choices which would change the defaults //! very useful to handle multiple domain on the same machine for example while (1) { MENU_STATUS code = dia.edit (MSG_U(T_ADD_USER,"User") ,MSG_U(I_ADD_USER,"Add user") ,help_formclient ,nof ,MENUBUT_ACCEPT|MENUBUT_QUIT); // Save dialog entries dia.save(); if (code == MENU_CANCEL || code == MENU_QUIT || code == MENU_ESCAPE){ /* Exit */ break; } else if (code == MENU_ACCEPT){ /* Add new entry */ /* Check that new name is specified */ if (user.is_empty()){ // FIXME translation xconf_error("Account name not specified"); } else { /* Check if this entry exist */ ldap.filter.setfromf("%s=%s",ldap.primary_key.get(),user.get()); int n = ldap.search(); if (n==0) { /* Add new entry */ ldap.dn.setfromf("dn: %s=%s,%s,%s", ldap.primary_key.get(), user.get(), ldap.dn_prefix.get(), ldap.bind.base.get()); /* Now add a generic set of required attributes and classes, so the new objects will be accepted in the directory. We need a way to let the user customize this instead of hardcoding it. Some kind of template, or maybe it is possible to use the existing schema files in some way. Maybe the classes could be specified with the formfield system. Note : start of this with support for default parameter in form */ // we will need this latter firstpluslast.setfrom(firstname); if (firstpluslast.getlen()>1) { firstpluslast.append(" "); } firstpluslast.append(lastname.get()); if (firstpluslast.getlen()<3) { // user was lazy firstpluslast.setfrom(user); // can't be empty } // Often needed ldap.oc_add("top"); // nis account ldap.oc_add("posixAccount"); ldap.oc_add("posixGroup"); ldap.oc_add("account"); ldap.at_set("uid",user.get()); ldap.at_set("cn",firstpluslast.get()); // Request free uidnumber ldap.at_set("uidNumber",ldap_get_free_uidnumber(profile_name)); // Set to uidbumber for now ldap.at_set("gidNumber",ldap.at_get("uidNumber")); // Defaults (mostly taken from form) // home directory SSTRING * temphome; temphome =defs.getdefault("homedirectory"); if (temphome) { ldap.at_set("homeDirectory",temphome->get()); } else { ldap.at_set("homeDirectory","/home/ldapuser"); // Needed for NIS schema } // login shell SSTRING * templogin; templogin =defs.getdefault("loginShell"); if (templogin) { ldap.at_set("loginShell",templogin->get()); } else { ldap.at_set("loginShell","/bin/false"); // Needed for NIS schema } // Gecos SSTRING * tempgecos; tempgecos =defs.getdefault("Gecos"); if (tempgecos) { ldap.at_set("Gecos",tempgecos->get()); } else { ldap.at_set("Gecos",firstpluslast.get()); } // Host login access (MULTI) //ldap.at_add("host","localhost"); // person ldap.oc_add("person"); ldap.oc_add("organizationalPerson"); ldap.oc_add("inetOrgPerson"); // sn SSTRING * tempsn; tempsn =defs.getdefault("sn"); if (lastname.getlen()>0) { ldap.at_set("sn",lastname.get()); } else if (tempsn) { ldap.at_set("sn",tempsn->get()); }; // givenName SSTRING * tempgivenname; tempgivenname =defs.getdefault("givenName"); if (firstname.getlen()>0) { ldap.at_set("givenName",firstname.get()); } else if (tempgivenname) { ldap.at_set("givenName",tempgivenname->get()); } // organization SSTRING * temporga; temporga =defs.getdefault("ou"); if (temporga) { ldap.at_set("ou",temporga->get()); } // mail ldap.oc_add("inetLocalMailRecipient"); // mail accounts (MULTI) SSTRING tempmail; SSTRING * tempdomain; tempdomain =defs.getdefault("mail"); if (tempdomain) { // user@domain.com ie pdupond@domain.com tempmail.setfrom(user.get()); tempmail.append("@"); tempmail.append(tempdomain->get()); tempmail.to_lower(); ldap.at_add("mail",tempmail.get()); // firstname.lastname@domain.com tempmail.setfrom(firstname.get()); tempmail.append("."); tempmail.append(lastname.get()); tempmail.append("@"); tempmail.append(tempdomain->get()); tempmail.to_lower(); ldap.at_add("mail",tempmail.get()); SSTRING firstname2(firstname); firstname2.truncate(1); // we only wan't the first letter to make p.dupond tempmail.setfrom(firstname2.get()); tempmail.append("."); tempmail.append(lastname.get()); tempmail.append("@"); tempmail.append(tempdomain->get()); tempmail.to_lower(); ldap.at_add("mail",tempmail.get()); } // Add account to directory ldap.add(); if (ldap.set_password(user.get(),password.get())) { //FIXME: translation xconf_error("Password not created ! It is empty !"); } else { //FIXME: translation xconf_notice("User created and Password set"); } // FIXME : this is not coherent with the userinterface // because the user is added before the user says accept and if he cancels, // it is naturel to think that the user was not added ! // Customize account FIXME: It should be possible to custmoize before adding to dir. Need editdialog for CONFDB ldap_form_edituser(profile_name,user.get()); logf(LOG_NOTICE,"Adding user %s to directory %s",user.get(),profile_name); break; } else { //FIXME : translation xconf_error("Account already in use"); } } } } } /* LDAPCLIENT search */ void ldap_form_search(const char *profile_name) { SSTRING form_key; LDAPOBJECT ldap(profile_name); int nof = 0; DIALOG dia; dia.newf_info ("Profile",profile_name); // dia.newf_title(MSG_U(F_SEARCH_FORM,"Search"),1,"",MSG_R(F_SEARCH_FORM)); dia.newf_str (MSG_U(F_SEARCH_KEY,"Search key"),form_key); /* Extra buttons */ dia.setbutinfo (MENU_USR1,MSG_R(B_SEARCH),MSG_R(X_SEARCH)); while (1) { MENU_STATUS code = dia.edit (MSG_U(T_FORM_SEARCH,"Search") ,MSG_U(I_FORM_SEARCH,"Search directory") ,help_formclient ,nof ,MENUBUT_USR1|MENUBUT_QUIT); // Save dialog entries dia.save(); /* Refresh dialog */ if (code == MENU_CANCEL || code == MENU_QUIT || code == MENU_ESCAPE){ /* Exit */ break; } else if (code == MENU_USR1 || code == MENU_ACCEPT){ /* Search */ SSTRINGS dnlist; D(debugf(4,"search : key =%s , formkey=%s \n",ldap.primary_key.get(),form_key.get())); ldap.search_base.setfromf("%s,%s",ldap.dn_prefix.get(),ldap.bind.base.get()); ldap.filter.setfromf("(%s=%s*)",ldap.primary_key.get(),form_key.get()); int n = ldap.search_list_val(dnlist,ldap.primary_key.get()); D(debugf(6,"fin search : n=%d\n",n)); if (n > 1) { // Multiple results SSTRING choice; ldaplist(dnlist,choice); ldap.search_base.setfromf("%s,%s",ldap.dn_prefix.get(),ldap.bind.base.get()); ldap.filter.setfromf("%s=%s",ldap.primary_key.get(),choice.get()); } n = ldap.search(); if (n == 1) { form_key.setfrom(ldap.at_get(ldap.primary_key.get())); ldap_form_edituser(profile_name,form_key.get()); } else { xconf_notice("No matches found"); } } } } /* Search result list */ int ldaplist(SSTRINGS&lst,SSTRING&choice) { DIALOG_LISTE *dia = NULL; int nof = 0; while (1){ if (dia == NULL){ dia = new DIALOG_LISTE; int n = lst.getnb(); dia->newf_head ("",MSG_U(F_DNLIST,"Search result")); for (int i=0; inew_menuitem (lst.getitem(i)->get(),""); } } MENU_STATUS code = dia->editmenu (MSG_U(T_DNLIST,"Select") ,MSG_U(I_DNLIST,"Select one") ,help_ldapuser ,nof ,0); if (code == MENU_QUIT || code == MENU_ESCAPE){ break; } else if (nof >=0 && nof < lst.getnb()){ choice = lst.getitem(nof)->get(); break; } } delete dia; return nof; } /* LDAPCLIENT password sync */ int ldap_pwsync(const char *username, const char *domain, const char *new_password) { int ret = -1; const char *profile_name; // Lookup domain profile char fpath[PATH_MAX]; sprintf(fpath,"%s/%s",PROFILE_DIR,domain); if (!strcmp(domain,"/")) { // Maindomain profile profile_name = "userinfo"; D(debugf(4,"ldap_pwsync: main domain: %s\n",domain)); } else if (fopen(fpath,"r")){ // Virtual domain profile profile_name = domain; D(debugf(4,"ldap_pwsync: virtual domain: %s\n",domain)); } else { // No profile D(debugf(4,"ldap_pwsync: no profile: %s\n",domain)); return ret; } LDAPOBJECT ldap(profile_name); if (!mode_posix_accounts && ldap.c_profile->getvalnum("profile","userinfo_comng",0)){ ret = ldap.set_password(username,new_password); } return ret; } /* LDAPCLIENT password */ void ldap_form_password(const char*user_name,const char *profile_name) { SSTRING password; LDAPOBJECT ldap(profile_name); int nof = 0; DIALOG dia; dia.newf_info ("Profile",profile_name); dia.newf_info ("User",user_name); dia.newf_pass (MSG_U(F_PASSWORD,"Password"),password); while (1) { MENU_STATUS code = dia.edit (MSG_U(T_FORM_PASSWORD,"Password") ,MSG_U(I_FORM_PASSWORD,"Enter new password") ,help_formclient ,nof ,MENUBUT_ACCEPT|MENUBUT_QUIT); // Save dialog entries dia.save(); /* Refresh dialog */ if (code == MENU_CANCEL || code == MENU_QUIT || code == MENU_ESCAPE){ /* Exit */ break; } else if (code == MENU_ACCEPT){ /* Change password */ if (ldap.set_password(user_name,password.get())) { xconf_error("Password not changed"); } else { xconf_notice("Password changed"); break; } } } } /*! Migrate users from system password files to ldap directory. */ int ldap_migrate_users(const char *profile_name){ USERS users; GROUPS groups; LDAPOBJECT ldap(profile_name); char ignore_admin = true; char ignore_special = true; char migrate_users = true; char migrate_groups = true; char migrate_members = true; int min_uid = 500; // Min uid to import int max_uid = 60000; // Max uid to import int min_gid = 500; // Min gid to import int max_gid = 60000; // Max gid to import int user_count = 0; int group_count = 0; int member_count = 0; char buf[256]; char dn[256]; int ret = -1; int i = 0; int nof; DIALOG dia; dia.newf_chk ("Admin accounts",ignore_admin,"Ignore"); dia.newf_chk ("Special accounts",ignore_special,"Ignore"); dia.newf_chk ("Users",migrate_users,"Include"); dia.newf_chk ("Groups",migrate_groups,"Include"); dia.newf_chk ("Members",migrate_members,"Include"); dia.newf_num ("Min UID",min_uid); dia.newf_num ("Max UID",max_uid); dia.newf_num ("Min GID",min_gid); dia.newf_num ("Max GID",max_gid); while (1){ MENU_STATUS code = dia.edit (MSG_U(T_MIGRATE,"Migrate") ,MSG_U(I_MIGRATE, "This will migrate user accounts from the local\n" "system files into the current directory\n") ,help_ldap ,nof); if (code == MENU_CANCEL || code == MENU_ESCAPE){ break; } else if (code == MENU_ACCEPT && xconf_yesno(MSG_U(T_MIGRATE_OK,"LDAP user migration"), MSG_U(I_MIGRATE_OK, "Migrating users are usually done for setting up a new directory.\n" "Are you sure you want to do this now?\n"), help_ldap) == MENU_YES) { if (migrate_groups) { int n=0; // Count migrated groups for (i=0;igetgid() < min_gid) || (group->getgid() > max_gid)) { D(debugf(5,"ignore group (%s)",group->getname())); continue; } ldap.reset_data(); ldap.dn.setfromf("dn: cn=%s,%s,%s", group->getname(), ldap.group_prefix.get(), ldap.bind.base.get()); // Add group object ldap.at_set("cn",group->getname()); ldap.at_set("gidNumber",group->getgid()); ldap.oc_add("top"); ldap.oc_add("posixGroup"); ldap.add(); n++; D(debugf(4,":migrate group: (%s) %i",ldap.dn.get(),n)); } group_count = n; logf(LOG_NORMAL,"Migrated %d groups using profile %s",n,profile_name); } if (migrate_users) { int n=0; // Count migrated accounts for (i=0;iis_admin()) || (ignore_special && user->is_special()) || (user->getuid() < min_uid) || (user->getuid() > max_uid)) { D(debugf(5,"ignore user (%s)",user->getname())); continue; } ldap.reset_data(); sprintf(dn,"dn: %s=%s,%s,%s", ldap.primary_key.get(), user->getname(), ldap.dn_prefix.get(), ldap.bind.base.get()); //if (ldap.exist(dn)) continue; // Already exist ldap.dn.setfrom(dn); ldap.at_set("uid",user->getname()); ldap.at_set("cn",user->getname()); ldap.at_set("sn",user->getname()); ldap.at_set("uidNumber",user->getuid()); ldap.at_set("gidNumber",user->getgid()); ldap.at_set("homeDirectory",user->gethome()); ldap.at_set("loginShell",user->getshell()); ldap.at_set("GECOS",user->getgecos()); sprintf(buf,"{crypt}%s",user->getpwd()); // FIXME: Something was broken here after the base64 password hack... // ldap.at_set("userPassword",buf); ldap.oc_add("top"); ldap.oc_add("account"); ldap.oc_add("posixAccount"); ldap.oc_add("inetLocalMailRecipient"); ldap.oc_add("organizationalPerson"); ldap.oc_add("inetOrgPerson"); ldap.add(); n++; D(debugf(4,":migrate user (%s) %i",ldap.dn.get(),n)); if (migrate_members) { SSTRING groups_str; SSTRINGS groups_list; const char *group_name = NULL; groups.getalt(user->getname(),groups_str); D(debugf(4,":migrate grouplist user=%s group_str=%s", user->getname(), groups_str.get())); str_splitline(groups_str.get(),' ',groups_list); for (int i=0; iget(); ldap.reset_data(); ldap.dn.setfromf("dn: cn=%s,%s,%s", group_name, ldap.group_prefix.get(), ldap.bind.base.get()); ldap.at_add("memberuid",user->getname()); ldap.modify(); member_count++; D(debugf(4,":migrate: added member %s to group %s", user->getname(), group_name)); } } } user_count = n; logf(LOG_NORMAL,"Migrated %d users using profile %s",n,profile_name); } xconf_notice("Migratation result\n" "\tUsers imported:\t%d\n" "\tGroups imported:\t%d\n" "\tMembers defined:\t%d\n" ,user_count,group_count,member_count); ret = i; } } return ret; } /*! Determine the next free uidnumber available for a new account. Return the free uidnumber if it is available. Return -1 if it could not be determined, for some reason. This is first attempt to implement a usable method for locating free uids. The principle is simple: Locate the highest uid in use by the directory, increment it by one, and return the result. Also reserve a range in the top and the bottom of the uid range (or rather, define the range available). This method is pretty slow and weak, but should hopefully work for small directories. */ static int ldap_get_free_id(const char *profile_name, const char *attr) { int free_uid = -1; SSTRING tmp; tmp.setfromf ("%s-%s",profile_name,attr); int lastid = linuxconf_getvalnum (K_LASTID,tmp.get(),-1); LDAPOBJECT ldap(profile_name); int min_uid, // The minimum uid number available max_uid; // The maximum uid number available min_uid = ldap.c_profile->getvalnum("profile","min_uid",1000); max_uid = ldap.c_profile->getvalnum("profile","max_uid",65000); if (lastid == -1){ // We have no idea of the first free ID. So we scan the database SSTRINGS ids; ldap.filter.setfromf("%s=*",attr); ldap.search_list_val(ids,attr); // Returns a list with the currently used ids int top_uid, // The highest uid number in use top_idx; // The list index of the highest number top_uid = min_uid - 1; // Start just below the valid uid range D(debugf(5,"maxuid=%i minuid=%i top_uid=%i",max_uid,min_uid,top_uid)); for (int i=0; igetval(); // Compare and range check if (cur_uid > top_uid && cur_uid >= min_uid && cur_uid < max_uid){ // This was higher and valid top_uid = cur_uid; top_idx = i; } } free_uid = top_uid + 1 ; // Let's try with the next uid and range check it below if (free_uid < min_uid || free_uid >= max_uid) { // Not valid - log it for now, make a popup error later free_uid = -1; logf(LOG_ERROR,"No id number available for new account/group (min=%i, max=%i, top=%i)" ,min_uid,max_uid,top_uid); } }else{ // We have already scan the database once to get the last // id. // Now we just increment it until we find an unused one. while (lastid < max_uid){ lastid++; SSTRINGS ids; ldap.filter.setfromf("%s=%d",attr,lastid); ldap.search_list_val(ids,attr); // Returns a list with the currently used ids if (ids.getnb()==0){ free_uid = lastid; break; } } } if (free_uid != -1){ linuxconf_replace (K_LASTID,tmp.get(),free_uid); linuxconf_save(); } D(debugf(4,"<--useradmin::ldap_get_free_uid return freeuid = %i",free_uid)); return free_uid; } int ldap_get_free_uidnumber(const char *profile_name) { return ldap_get_free_id (profile_name,"uidNumber"); } int ldap_get_free_gidnumber(const char *profile_name) { return ldap_get_free_id (profile_name,"gidNumber"); }