diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index bab8e1e..4000f7e 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -15,8 +15,11 @@ * * "30 * * * * /var/local/submitty/bin/accounts.php" * - * You may specify the term on the command line with "-t". + * You may specify on the command line: + * "-t [term code]" make auth accounts for [term code]. * "-g" can be used to guess the term by the server's calendar month and year. + * "-a" will make auth accounts for all instructors and all active courses. + * "-r" will remove grader and student auth accounts from inactive courses. * For example: * * ./accounts.php -t s18 @@ -26,22 +29,22 @@ * @author Peter Bailie, Systems Programmer (RPI dept of computer science) */ -error_reporting(0); -ini_set('display_errors', 0); +error_reporting(null); +ini_set('display_errors', '0'); //Database access -define('DB_LOGIN', 'hsdbu'); -define('DB_PASSWD', 'hsdbu_pa55w0rd'); +define('DB_LOGIN', 'submitty_dbuser'); +define('DB_PASSWD', 'submitty_dbuser_pa55W0rd'); define('DB_HOST', 'localhost'); -define('DB_NAME', 'submitty'); //Location of accounts creation error log file -define('ERROR_LOG_FILE', '/var/local/submitty/bin/accounts_errors.log'); +define('ERROR_LOG_FILE', 'accounts_script_error.log'); //Where to email error messages so they can get more immediate attention. //Set to null to not send email. define('ERROR_EMAIL', 'sysadmins@lists.myuniversity.edu'); + /* SUGGESTED SETTINGS FOR TIMEZONES IN USA ------------------------------------- * * Eastern ........... America/New_York @@ -61,90 +64,227 @@ date_default_timezone_set('America/New_York'); //Start process -main(); -exit(0); - -/** Main process */ -function main() { - //IMPORTANT: This script needs to be run as root! - if (posix_getuid() !== 0) { - exit("This script must be run as root." . PHP_EOL); +new make_accounts(); + +/** Class constructor manages script workflow */ +class make_accounts { + + /** @static @var resource pgsql database connection */ + private static $db_conn; + /** @static @var string what workflow to process */ + private static $workflow; + /** @static @var array paramater list for DB query */ + private static $db_params; + /** @static @var string DB query to be run */ + private static $db_query; + /** @static @var string function to call to process $workflow */ + private static $workflow_function; + /** @static @var array user_id list of 'auth only accounts', read from /etc/passwd */ + private static $auth_only_accounts; + + public function __construct() { + //IMPORTANT: This script needs to be run as root! + if (posix_getuid() !== 0) { + exit("This script must be run as root." . PHP_EOL); + } + + //Init class properties, quit on error. + if ($this->init() === false) { + exit(1); + } + + //Do workflow, quit on error. + if ($this->process() === false) { + exit(1); + } + + //All done. + exit(0); } - //Check for semester among CLI arguments. - $semester = cli_args::parse_args(); - if ($semester === false) { - exit(1); + public function __destruct() { + //Close DB connection, if it exists. + if (pg_connection_status(self::$db_conn) === PGSQL_CONNECTION_OK) { + pg_close(self::$db_conn); + } + } + + /** + * Initialize class properties, based on self::$workflow + * + * @access private + * @return boolean TRUE on success, FALSE when there is a problem. + */ + private function init() { + //Check CLI args. + if (($cli_args = cli_args::parse_args()) === false) { + return false; + } + self::$workflow = $cli_args[0]; + self::$db_params = is_null($cli_args[1]) ? array() : array($cli_args[1]); + + //Define database query AND system call based on workflow. + switch(self::$workflow) { + case 'term': + self::$db_query = <<1 AND c.status=1) +SQL; + self::$workflow_function = 'add_user'; + break; + + case 'clean': + //'clean' workflow requires list of 'auth only accounts' from /etc/passwd. + self::$auth_only_accounts = array(); + if (($fh = fopen('/etc/passwd', 'r')) === false) { + $this->logit("Cannot open '/etc/passwd' to check for auth only accounts."); + return false; + } + while (($row = fgetcsv($fh, 0, ':')) !== false) { + if (strpos($row[4], 'auth only account') !== false) { + self::$auth_only_accounts[] = $row[0]; + } + } + fclose($fh); + self::$db_query = <<1 AND c.status<>1 +SQL; + self::$workflow_function = 'remove_user'; + break; + + default: + $this->log_it("Invalid self::$workflow during init()"); + return false; + } + + //Signal success + return true; } - $user_list = get_user_list($semester); - if ($user_list !== false) { - foreach($user_list as $user) { - //We don't care if user already exists as adduser will skip over any account that already exists. - system ("/usr/sbin/adduser --quiet --home /tmp --gecos 'RCS auth account' --no-create-home --disabled-password --shell /usr/sbin/nologin {$user} > /dev/null 2>&1"); + /** + * Process workflow + * + * @access private + * @return boolean TRUE on success, FALSE when there is a problem. + */ + private function process() { + //Connect to database. Quit on failure. + if ($this->db_connect() === false) { + $this->log_it("Submitty Auto Account Creation: Cannot connect to DB {$db_name}."); + return false; } + + //Get user list based on command. Quit on failure. + if (($result = pg_query_params(self::$db_conn, self::$db_query, self::$db_params)) === false) { + $this->log_it("Submitty Auto Account Creation: Cannot read user list from {$db_name}."); + return false; + } + + $num_rows = pg_num_rows($result); + for ($i = 0; $i < $num_rows; $i++) { + $user = pg_fetch_result($result, $i, 'user_id'); + call_user_func(array($this, self::$workflow_function), $user); + } + + //Signal success + return true; } -} -/** - * Retrieve user list from courses_users - * - * @param string $semester - * @return array of user ids on success, boolean false on failure. - */ -function get_user_list($semester) { - $db_user = DB_LOGIN; - $db_pass = DB_PASSWD; - $db_host = DB_HOST; - $db_name = DB_NAME; - $db_conn = pg_connect("host={$db_host} dbname={$db_name} user={$db_user} password={$db_pass}"); - if ($db_conn === false) { - log_it("Submitty Auto Account Creation: Cannot connect to DB {$db_name}."); - return false; + /** + * Establish connection to Submitty Database + * + * @access private + * @return boolean TRUE on success, FALSE when there is a problem. + */ + private function db_connect() { + $db_user = DB_LOGIN; + $db_pass = DB_PASSWD; + $db_host = DB_HOST; + self::$db_conn = pg_connect("host={$db_host} dbname=submitty user={$db_user} password={$db_pass} sslmode=prefer"); + if (pg_connection_status(self::$db_conn) !== PGSQL_CONNECTION_OK) { + $this->log_it(pg_last_error(self::$db_conn)); + return false; + } + + //Signal success + return true; + } + + /** + * Add a user for authentication with PAM. + * + * @access private + * @param string $user User ID to added. + */ + private function add_user($user) { + system("/usr/sbin/adduser --quiet --home /tmp --gecos 'auth only account' --no-create-home --disabled-password --shell /usr/sbin/nologin {$user} > /dev/null 2>&1"); } - $db_query = pg_query_params($db_conn, "SELECT DISTINCT user_id FROM courses_users WHERE semester=$1;", array($semester)); - if ($db_query === false) { - log_it("Submitty Auto Account Creation: Cannot read user list from {$db_name}."); - return false; + /** + * Remove an 'auth only user' from authenticating with PAM + * + * @access private + * @param string $user User ID to be checked/removed. + */ + private function remove_user($user) { + //Make sure $user is an "auth only account" before removing. + if (array_search($user, self::$auth_only_accounts) !== false) { + system("/usr/sbin/deluser --quiet {$user} > /dev/null 2>&1"); + } } - $user_list = pg_fetch_all_columns($db_query, 0); - pg_close($db_conn); - return $user_list; -} + /** + * Log message to email and text files + * + * @access private + * @param string $msg + */ + private function log_it($msg) { + $msg = date('m/d/y H:i:s : ', time()) . $msg . PHP_EOL; + error_log($msg, 3, ERROR_LOG_FILE); + + if (!is_null(ERROR_EMAIL)) { + error_log($msg, 1, ERROR_EMAIL); + } + } +} //END class make_accounts /** - * Log message to email and text files + * class to parse command line arguments * - * @param string $msg + * @static */ -function log_it($msg) { - $msg = date('m/d/y H:i:s : ', time()) . $msg . PHP_EOL; - error_log($msg, 3, ERROR_LOG_FILE); - - if (!is_null(ERROR_EMAIL)) { - error_log($msg, 1, ERROR_EMAIL); - } -} - -/** @static class to parse command line arguments */ class cli_args { - /** @var array holds all CLI argument flags and their values */ - private static $args; /** @var string usage help message */ - private static $help_usage = "Usage: accounts.php [-h | --help] (-t [term code] | -g)" . PHP_EOL; + private static $help_usage = "Usage: accounts.php [-h | --help] (-a | -t [term code] | -g | -r)" . PHP_EOL; /** @var string short description help message */ private static $help_short_desc = "Read student enrollment from Submitty DB and create accounts for PAM auth." . PHP_EOL; /** @var string argument list help message */ private static $help_args_list = <<= 8, s(u)mmer are months 6 and 7. - //if ($month <= 5) {...} else if ($month >= 8) {...} else {...} - $month = intval(date("m", time())); - $year = date("y", time()); - return ($month <= 5) ? "s{$year}" : (($month >= 8) ? "f{$year}" : "u{$year}"); - } - case array_key_exists('t', self::$args): - return self::$args['t']; + case array_key_exists('a', $args): + return array("active", null); + case array_key_exists('t', $args): + return array("term", $args['t']); + case array_key_exists('g', $args): + //Guess current term + //(s)pring is month <= 5, (f)all is month >= 8, s(u)mmer are months 6 and 7. + //if ($month <= 5) {...} else if ($month >= 8) {...} else {...} + $month = intval(date("m", time())); + $year = date("y", time()); + return ($month <= 5) ? array("term", "s{$year}") : (($month >= 8) ? array("term", "f{$year}") : array("term", "u{$year}")); + case array_key_exists('r', $args): + return array("clean", null); default: print self::$help_usage . PHP_EOL; return false; @@ -200,7 +337,7 @@ private static function print_help() { //Arguments list print self::$help_args_list . PHP_EOL; } -} +} //END class parse_args /* EOF ====================================================================== */ ?> diff --git a/pam_accounts/readme.md b/pam_accounts/readme.md index deda894..d026f1c 100644 --- a/pam_accounts/readme.md +++ b/pam_accounts/readme.md @@ -1,15 +1,27 @@ # PAM Authentication Accounts Script -Readme June 26, 2018 +Readme August 7, 2018 ### accounts.php This is a command line script that will read the submitty 'master' database and -create PAM authentication accounts, password checked by Kerberos, for all -Submitty users. *This script is not used with database authentication.* +create authentication accounts for all Submitty users for PAM. + +**This script is not used with Submitty database authentication.** --- -The semester must be either manually specified as command line argument -`-t`, or guessed by calendar month and year with command line argument `-g`. +One of the following command line parameters is required. They are listed +in order of precedence. e.g. `-a` overrides `-t`. + +- `-a` Create authentication accounts for all instructor users of all courses +and student, grader, and mentor users in all *active* courses, regardless of +term. +- `-t [term code]` Specify which term to create authentication accounts. All +course enrollments of `[term code]` are processed regardless of course status. +- `-g` Guess the term code, based on calendar month and year, to create +authentication accounts. Otherwise behaves like `-t`. +- `-r` Remove student, grader, and mentor authentication accounts for all +*inactive* courses, regardless of term code. Instructor authentication accounts +are not removed. For example: @@ -25,12 +37,10 @@ will follow the pattern of TYY, where - T is the term - **s** is for Spring (Jan - May) - **u** is for Summer (Jun - Jul) - - **f** is for Fall (Aug-Dec) + - **f** is for Fall (Aug - Dec) - YY is the two digit year - e.g. April 15, 2018 will correspond to "s18" (Spring 2018). -`-g` and `-t` are mutually exclusive. - --- accounts.php is intended to be run as a cron job.