3 * FusionForge account functions
5 * Copyright 1999-2001, VA Linux Systems, Inc.
6 * Copyright 2010, Franck Villaume - Capgemini
7 * Copyright 2012,2016, Franck Villaume - TrivialDev
8 * Copyright (C) 2015 Inria (Sylvain Beucler)
10 * This file is part of FusionForge. FusionForge is free software;
11 * you can redistribute it and/or modify it under the terms of the
12 * GNU General Public License as published by the Free Software
13 * Foundation; either version 2 of the Licence, or (at your option)
16 * FusionForge is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License along
22 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 forge_define_config_item_bool('check_password_strength', 'core', 'true');
29 * pw_weak() - checks if password is weak
31 * @param string $pw the password
32 * @return false if password ok, string with description of problem if password ko.
35 function pw_weak($pw) {
36 // password ok if contains at least 1 uppercase letter, 1 lowercase, 1 digit and 1 non-alphanumeric
37 if (!preg_match('/[[:lower:]]/', $pw)) {
38 return _("Password must contain at least one lowercase letter.");
40 if (!preg_match('/[[:upper:]]/', $pw)) {
41 return _("Password must contain at least one uppercase letter.");
43 if (!preg_match('/[[:digit:]]/', $pw)) {
44 return _("Password must contain at least one digit.");
46 if (!preg_match('/[^[:alnum:]]/', $pw)) {
47 return _("Password must contain at least one non-alphanumeric character.");
53 * account_pwvalid() - Validates a password
55 * @param string $pw The plaintext password string
56 * @return bool true on success/false on failure
59 function account_pwvalid($pw) {
60 if (strlen($pw) < 8) {
61 $GLOBALS['register_error'] = _('Password must be at least 8 characters.');
64 if (forge_get_config('check_password_strength')) {
65 if ($msg = pw_weak($pw)) {
66 $GLOBALS['register_error'] = $msg;
74 * account_namevalid() - Validates a login username
76 * @param string $name The username string
77 * @param bool $unix Check for an unix username
78 * @param bool $check_exists
79 * @return bool true on success/false on failure
82 function account_namevalid($name, $unix=false, $check_exists=true) {
85 // If accounts comes from ldap and no shell access, then disable controls.
86 $pluginManager = plugin_manager_get_object();
87 if (!forge_get_config('use_shell') && $pluginManager->PluginIsInstalled('ldapextauth')) {
93 if (strrpos($name,' ') > 0) {
94 $GLOBALS['register_error'] = _('There cannot be any spaces in the login name.');
99 if (strlen($name) < 3) {
100 $GLOBALS['register_error'] = _('Name is too short. It must be at least 3 characters.');
103 if (strlen($name) > 32) {
104 $GLOBALS['register_error'] = _('Name is too long. It must be less than 32 characters.');
108 if (!preg_match('/^[a-z0-9][-a-z0-9_\.]+\z/', $name)) {
109 $GLOBALS['register_error'] = _('Illegal character in name.');
113 // avoid ambiguity with UID/GID, especially in system commands (chown, chgrp, etc.)
114 if (!preg_match('/[a-z]/', $name)) {
115 $GLOBALS['register_error'] = _('Name contains only digits. It must contains at least 1 letter.');
120 $system_user = forge_get_config('system_user');
121 $system_user_ssh_akc = forge_get_config('system_user_ssh_akc');
122 $regExpReservedNames = "^(root|bin|daemon|adm|lp|sync|shutdown|halt|mail|news|"
123 . "uucp|operator|games|mysql|httpd|nobody|dummy|www|cvs|shell|ftp|irc|"
124 . "debian|ns|download|{$system_user}|{$system_user_ssh_akc})$";
125 if( preg_match("/$regExpReservedNames/i", $name) ) {
126 $GLOBALS['register_error'] = _('Name is reserved.');
129 if (forge_get_config('use_shell') && $check_exists) {
130 if (exec("getent passwd $name") != "" ){
131 $GLOBALS['register_error'] = _('That username already exists.');
134 if (exec("getent group $name") != "" ){
135 $GLOBALS['register_error'] = _('That username already exists.');
144 * account_groupnamevalid() - Validates an account group name
146 * @param string $name The group name string
147 * @return bool true on success/false on failure
150 function account_groupnamevalid($name) {
151 if (!account_namevalid($name, 1)) {
155 $regExpReservedGroupNames = "^(www[0-9]?|cvs[0-9]?|shell[0-9]?|ftp[0-9]?|"
156 . "irc[0-9]?|news[0-9]?|mail[0-9]?|ns[0-9]?|download[0-9]?|pub|users|"
157 . "compile|lists|slayer|orbital|tokyojoe|webdev|projects|cvs|monitor|"
158 . "mirrors?|.*_scmro|.*_scmrw)$";
159 if(preg_match("/$regExpReservedGroupNames/i",$name)) {
160 $GLOBALS['register_error'] = _('Name is reserved for DNS purposes.');
164 if(preg_match("/_/",$name)) {
165 $GLOBALS['register_error'] = _('Group name cannot contain underscore for DNS reasons.');
173 * genchr - Generate a random character
175 * This is a local function used for account_salt()
177 * @return string A random character
182 $num = util_randnum(46, 122);
183 } while ( ( $num > 57 && $num < 65 ) || ( $num > 90 && $num < 97 ) );
188 * account_gensalt - A random salt generator
190 * @returns string The random salt string
193 function account_gensalt(){
195 // ncommander: modified for cipher selection
196 // crypt() selects the cipher based on
201 switch(forge_get_config('unix_cipher')) {
206 $salt_prefix = '$1$';
210 $salt_prefix = '$5$rounds=5000$';
214 $salt_prefix = '$2y$10$';
219 $salt_prefix = '$6$rounds=5000$';
225 for ($i = 0; $i < $salt_size; $i++) {
228 $salt = $salt_prefix.$salt;
234 * account_genunixpw - Generate unix password
236 * @param string $plainpw The plaintext password string
237 * @return string The encrypted password
240 function account_genunixpw($plainpw) {
241 // ncommander: Support clear password hashing
242 // for usergroup_plain.php
244 if (strcasecmp(forge_get_config('unix_cipher'), 'Plain') == 0) {
247 return crypt($plainpw,account_gensalt());
252 * account_get_user_default_shell - return default user shell
254 * @return string the shell absolute path.
256 function account_get_user_default_shell() {
257 $user_default_shell = forge_get_config('user_default_shell');
258 if (! isset($user_default_shell)) {
259 // same as in DB schema before that config var was introduced
260 $user_default_shell = '/bin/bash';
262 return $user_default_shell;
266 * account_getavailableshells - return available shells for the users
268 * @param bool $add_user_default_shell
269 * @return array Available shells
271 function account_getavailableshells($add_user_default_shell = true) {
272 // we'd better use the shells defined inside the 'chroot' in {core/chroot}/etc/shells if it exists
273 $chroot = forge_get_config('chroot');
274 $shells_file = $chroot.'/etc/shells';
275 if(! file_exists($shells_file) ) {
276 // otherwise, fallback to /etc/shells
277 $shells_file = '/etc/shells';
279 $shells = file($shells_file);
281 $out_shells = array();
282 foreach ($shells as $s) {
283 if (substr($s, 0, 1) == '#') {
286 $out_shells[] = chop($s);
288 // in most cases, we do need to add the default shell in case it wouldn't be in the ../etc/shells already (no regression)
289 if ($add_user_default_shell) {
290 $user_default_shell = account_get_user_default_shell();
291 if (! file_exists($user_default_shell) ) {
292 // we'll always add cvssh if no other default set ... TODO: explain why ?
293 $user_default_shell = "/bin/cvssh";
295 if (!in_array($user_default_shell, $out_shells)) {
296 $out_shells[count($out_shells)] = $user_default_shell;
303 * account_shellselects - Print out shell selects
305 * @param string $current the current shell
306 * @return string HTML code options for a select tag
308 function account_shellselects($current) {
311 $shells = account_getavailableshells();
314 for ($i = 0; $i < count($shells); $i++) {
315 $this_shell = $shells[$i];
317 if ($current == $this_shell) {
319 $html .= "<option selected=\"selected\" value=\"$this_shell\">$this_shell</option>\n";
321 // the last one is supposed to be the default, so select it if not found current shell to observe default settings
322 if ( ($i == (count($shells) - 1)) && (! $found)) {
323 $html .= "<option selected=\"selected\" value=\"$this_shell\">$this_shell</option>\n";
325 $html .= "<option value=\"$this_shell\">$this_shell</option>\n";
330 // add the current option but unselectable -> defaults to cvssh if no other option in {core/chroot}/etc/shells
331 $html .= "<option value=\"$current\" disabled=\"disabled\">$current</option>\n";
337 * account_user_homedir - Returns full path of user home directory
339 * @param string $user The username
340 * @return string home directory path
342 function account_user_homedir($user) {
343 return forge_get_config('homedir_prefix').'/'.$user;
347 * account_group_homedir - Returns full path of group home directory
349 * @param string $group The group name
350 * @return string home directory path
352 function account_group_homedir($group) {
353 return forge_get_config('groupdir_prefix').'/'.$group;
357 * checkKeys - Simple function that tries to check the validity of public ssh keys with a regexp.
358 * Exits with an error message if an invalid key is found.
360 * @param string $keys A string with a set of keys to check. Each key is delimited by a carriage return.
362 function checkKeys($keys) {
364 $key = strtok($keys, "\n");
365 while ($key !== false) {
367 if ((strlen($key) > 0) && ($key[0] != '#')) {
368 /* The encoded key is made of 0-9, A-Z ,a-z, +, / (base 64) characters,
369 ends with zero or up to three '=' and the length must be >= 512 bits (157 base64 characters).
370 The whole key ends with an optional comment. */
371 if ( preg_match("@^(((no-port-forwarding|no-X11-forwarding|no-agent-forwarding|no-pty|command=\"[^\"]+\"|from=\"?[A-Za-z0-9\.-]+\"?),?)*\s+)?(ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|ssh-dss|ssh-rsa)\s+[A-Za-z0-9+/]{68,}={0,2}(\s+.*)?$@", $key) === 0 ) { // Warning: we must use === for the test
372 $error_msg = sprintf(_('The following key has a wrong format: |%s|. Please, correct it by going back to the previous page.'),
373 htmlspecialchars($key));
374 session_redirect('/account/');
383 // c-file-style: "bsd"