├── .gitattributes ├── .gitignore ├── README.md ├── changepassword.php └── ldap.php /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | #packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper* 82 | 83 | # Installshield output folder 84 | [Ee]xpress 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish 98 | 99 | # Others 100 | [Bb]in 101 | [Oo]bj 102 | sql 103 | TestResults 104 | *.Cache 105 | ClientBin 106 | stylecop.* 107 | ~$* 108 | *.dbmdl 109 | Generated_Code #added for RIA/Silverlight projects 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | 118 | 119 | ############ 120 | ## Windows 121 | ############ 122 | 123 | # Windows image file caches 124 | Thumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | 130 | ############# 131 | ## Python 132 | ############# 133 | 134 | *.py[co] 135 | 136 | # Packages 137 | *.egg 138 | *.egg-info 139 | dist 140 | build 141 | eggs 142 | parts 143 | bin 144 | var 145 | sdist 146 | develop-eggs 147 | .installed.cfg 148 | 149 | # Installer logs 150 | pip-log.txt 151 | 152 | # Unit test / coverage reports 153 | .coverage 154 | .tox 155 | 156 | #Translations 157 | *.mo 158 | 159 | #Mr Developer 160 | .mr.developer.cfg 161 | 162 | # Mac crap 163 | .DS_Store 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Major Components: 2 | * changepassword.php - Searches for a WHMCS user's account in LDAP (with a matching email address) and changes that password when the password is changed in WHMCS. 3 | * ldap.php - Creates users in LDAP when they are created in WHMCS. 4 | 5 | Installation: 6 | * You will need to modify this script to match your own schema. I used phpLDAPadmin, exported an user, then copied the fields into the script exactly. Also, you will need to change the DN settings to match your own login information. 7 | * For ldap.php, you will also need to create a custom field for the user's LDAP username. Part of the code does a mysql query to find the user's LDAP username which corresponds to the userID which WHMCS returns. 8 | * Add the modified script to your whmcs/includes/hooks directory. 9 | 10 | Debugging: 11 | * You can use the WHMCS activity log. The script will create an entry if it fails. However, you may want to set static variables for each of the items in the function and try to add an user. 12 | -------------------------------------------------------------------------------- /changepassword.php: -------------------------------------------------------------------------------- 1 | $userid); 22 | $result2 = select_query($table2,$fields2,$where2); 23 | $data2 = mysql_fetch_array($result2); 24 | $email = $data2['email']; 25 | if (!$email) { 26 | logActivity('Could not find email for User ID: ' . $userid); 27 | } else { 28 | // logActivity('Email from WHMCS: ' . $email); 29 | } 30 | 31 | // search LDAP for matching email 32 | $sr=ldap_search($ds, "ou=people,dc=yourdomain,dc=com", "mail=$email"); 33 | if ($sr) { 34 | if (ldap_count_entries($ds, $sr) > 1) { 35 | // bad - multiple entries with that email found 36 | logActivity("Multiple users with that e-mail"); 37 | $info = ldap_get_entries($ds, $sr); 38 | $username = $info[0]["uid"][0]; 39 | } elseif (ldap_count_entries($ds, $sr) == 1) { 40 | // correct - only 1 entry with that email found 41 | $info = ldap_get_entries($ds, $sr); 42 | $username = $info[0]["uid"][0]; 43 | } else { 44 | // bad - user not found 45 | logActivity("No matching email in LDAP"); 46 | } 47 | } else { 48 | logActivity("LDAP Search Failure"); 49 | } 50 | 51 | // Generate SSHA hash 52 | mt_srand((double)microtime()*1000000); 53 | $salt = pack("CCCC", mt_rand(), mt_rand(), mt_rand(), mt_rand()); 54 | $group_info['userpassword'][0] = "{SSHA}" . base64_encode(pack("H*", sha1($password . $salt)) . $salt); 55 | $group_name = "uid=" . $username . ",ou=people,dc=yourdomain,dc=com"; 56 | 57 | logActivity($username); 58 | // change user password, but not if their WHMCS email doesn't match their LDAP email 59 | $s = ldap_modify($ds,$group_name,$group_info); 60 | 61 | if (!$s) { 62 | // logs error if ldap_mod_add fails 63 | logActivity($userid . " password change failure"); 64 | logActivity(ldap_error($ds)); 65 | } else { 66 | logActivity($username . " changed password successfully"); 67 | } 68 | 69 | ldap_close($ds); 70 | } else { 71 | logActivity("Unable to connect to LDAP server"); 72 | } 73 | 74 | } 75 | 76 | add_hook("ClientChangePassword",1,"change_ldap_pass"); 77 | 78 | ?> 79 | -------------------------------------------------------------------------------- /ldap.php: -------------------------------------------------------------------------------- 1 | "2", "relid"=>$userid); 18 | $result = select_query($table,$fields,$where); 19 | $data = mysql_fetch_array($result); 20 | $username = $data['value']; 21 | logActivity($username); 22 | 23 | $firstname = $vars['firstname']; 24 | $lastname = $vars['lastname']; 25 | $email = $vars['email']; 26 | $address = $vars['address1'] . " " . $vars['address2']; 27 | $postalcode = $vars['postcode']; 28 | $state = $vars['state']; 29 | $city = $vars['city']; 30 | $password = $vars['password']; 31 | $phonenumber = $vars['phonenumber']; 32 | 33 | 34 | // shadowlastchange requires days since last epoch 35 | $unixTimeDays = floor(time()/86400); 36 | 37 | // function will find the largest UID, then add 1 to generate a UID for the user 38 | // http://bakery.cakephp.org/articles/UncleBill/2006/10/15/using-ldap-as-a-database 39 | function findLargestUidNumber($ds) 40 | { 41 | $s = ldap_search($ds, "ou=people,dc=yourdomain,dc=com", 'uidnumber=*'); 42 | if ($s) 43 | { 44 | // there must be a better way to get the largest uidnumber, but I can't find a way to reverse sort. 45 | ldap_sort($ds, $s, "uidnumber"); 46 | 47 | $result = ldap_get_entries($ds, $s); 48 | $count = $result['count']; 49 | $biguid = $result[$count-1]['uidnumber'][0]; 50 | return $biguid; 51 | } 52 | return null; 53 | } 54 | $largestUID = findLargestUidNumber($ds); 55 | if ($largestUID == null) 56 | { 57 | logActivity("Unable to find largest UID"); 58 | } 59 | else { 60 | $generatedUID = $largestUID + 1; 61 | 62 | // construct array of information which will be added to LDAP 63 | $info['cn'][0] = $firstname; 64 | $info['description'][0] = "User account"; 65 | $info['displayName'][0] = $firstname . " " . $lastname; 66 | $info['gecos'] = $username; 67 | $info['sn'][0] = $lastname; 68 | $info['mail'][0] = $email; 69 | $info['objectclass'][0] = "inetOrgPerson"; 70 | $info['objectclass'][1] = "posixAccount"; 71 | $info['objectclass'][2] = "shadowAccount"; 72 | $info['gidnumber'][0] = "10000"; 73 | $info['homedirectory'][0] = "/home/" . $username; 74 | $info['homephone'][0] = $phonenumber; 75 | $info['l'][0] = $city; 76 | $info['loginshell'][0] = "/bin/bash"; 77 | $info['mobile'][0] = $phonenumber; 78 | $info['postalcode'][0] = $postalcode; 79 | $info['shadowlastchange'][0] = $unixTimeDays; 80 | $info['st'][0] = $state; 81 | $info['street'][0] = (string) $address; 82 | $info['title'][0] = "Member"; 83 | $info['uid'][0] = $username; 84 | $info['uidnumber'][0] = $generatedUID; 85 | 86 | // Generate SSHA hash 87 | mt_srand((double)microtime()*1000000); 88 | $salt = pack("CCCC", mt_rand(), mt_rand(), mt_rand(), mt_rand()); 89 | $info['userpassword'][0] = "{SSHA}" . base64_encode(pack("H*", sha1($password . $salt)) . $salt); 90 | 91 | $dn = "uid=" . $username . ",ou=people,dc=yourdomain,dc=com"; 92 | 93 | // add data to people OU 94 | $r = ldap_add($ds, $dn, $info); 95 | if (!$r) 96 | { 97 | logActivity(ldap_error($ds)); 98 | } 99 | 100 | // add user to members group 101 | $group_name = "cn=members,ou=groups,dc=yourdomain,dc=com"; 102 | $group_info['memberUid'] = $username; 103 | $t = ldap_mod_add($ds,$group_name,$group_info); 104 | 105 | if (!$t) 106 | { 107 | logActivity(ldap_error($ds)); 108 | } 109 | } 110 | ldap_close($ds); 111 | } else { 112 | logActivity("Unable to connect to LDAP server"); 113 | } 114 | } 115 | 116 | add_hook("ClientAdd",1,"create_ldap_account"); 117 | 118 | ?> 119 | --------------------------------------------------------------------------------