From 827a1f95c6dad8f99a5e43472d1e49872686715a Mon Sep 17 00:00:00 2001 From: pbailie Date: Tue, 31 Jul 2018 19:14:33 -0400 Subject: [PATCH 01/12] Process By Active Status pam_accounts/accounts.php WIP --- pam_accounts/accounts.php | 177 ++++++++++++++++++++++---------------- 1 file changed, 102 insertions(+), 75 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index bab8e1e..32f13a5 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -17,6 +17,7 @@ * * You may specify the term on the command line with "-t". * "-g" can be used to guess the term by the server's calendar month and year. + * "-s" will process accounts based on status and user role. * For example: * * ./accounts.php -t s18 @@ -61,72 +62,99 @@ date_default_timezone_set('America/New_York'); //Start process -main(); +make_accounts::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); - } +class make_accounts { - //Check for semester among CLI arguments. - $semester = cli_args::parse_args(); - if ($semester === false) { - exit(1); - } + private static $process; + private static $db_conn; + private static $db_queries = array( + 'term' => "SELECT DISTINCT user_id FROM courses_users WHERE semester=$1", + 'active' => "SELECT DISTINCT user_id FROM courses_users WHERE user_group=1 OR (user_group<>1 AND status=1)", + 'clean' => "SELECT DISTINCT user_id FROM courses_users WHERE user_group<>1 AND status<>1" + ); - $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"); + public static 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); } - } -} -/** - * 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; - } + //Check CLI args for instructions. Quit if set to false. + $command = cli_args::parse_args(); + if ($command === false) { + exit(1); + } + + self::$process = array_pad(explode(" ", $command), 2, null); - $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; + //Connect to database. Quit on failure. + if (!self::db_conn()) { + exit(1); + } + + //Get user list based on type of process. Quit on failure. + $query = self::$db_queries[self::$process[0]]; + $params = array(self::$process[1]); + $result = pg_query_params(self::$db_conn, $query, $params); + if ($result === false) { + self::log_it("Submitty Auto Account Creation: Cannot read user list from {$db_name}."); + exit(1); + } + + $user_list = pg_fetch_all_columns($result, 0); + + switch(self::$process[0]) { + case 'term': + case 'active': + 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"); + } + break; + case 'clean': + foreach($user_list as $user) { + //deluser + } + break; + default; + exit(1); + } } - $user_list = pg_fetch_all_columns($db_query, 0); - pg_close($db_conn); - return $user_list; -} + private static function db_conn() { + $db_user = DB_LOGIN; + $db_pass = DB_PASSWD; + $db_host = DB_HOST; + $db_name = DB_NAME; + self::$db_conn = pg_connect("host={$db_host} dbname={$db_name} user={$db_user} password={$db_pass}"); + if (self::$db_conn === false) { + self::log_it("Submitty Auto Account Creation: Cannot connect to DB {$db_name}."); + return false; + } -/** - * Log message to email and text files - * - * @param string $msg - */ -function log_it($msg) { - $msg = date('m/d/y H:i:s : ', time()) . $msg . PHP_EOL; - error_log($msg, 3, ERROR_LOG_FILE); + register_shutdown_function(function() { + pg_close(self::$db_conn); + }); + + return true; + } + + /** + * Log message to email and text files + * + * @param string $msg + */ + private 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); + if (!is_null(ERROR_EMAIL)) { + error_log($msg, 1, ERROR_EMAIL); + } } -} +} //END class make_accounts /** @static class to parse command line arguments */ class cli_args { @@ -134,17 +162,19 @@ 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)" . 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('a', self::$args): + return "active"; case array_key_exists('t', self::$args): - return self::$args['t']; + return "term " . self::$args['t']; + case array_key_exists('g', self::$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) ? "term s{$year}" : (($month >= 8) ? "term f{$year}" : "term u{$year}"); + case array_key_exists('r', self::$args): + return "clean"; default: print self::$help_usage . PHP_EOL; return false; @@ -200,7 +227,7 @@ private static function print_help() { //Arguments list print self::$help_args_list . PHP_EOL; } -} +} //END class parse_args /* EOF ====================================================================== */ ?> From e21738210a5694ca66d2a3f85ef0db2822f393c9 Mon Sep 17 00:00:00 2001 From: pbailie Date: Wed, 1 Aug 2018 19:10:34 -0400 Subject: [PATCH 02/12] accounts.php update pam_accounts/accounts.php WIP --- pam_accounts/accounts.php | 79 +++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index 32f13a5..a6906f6 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -67,13 +67,26 @@ class make_accounts { - private static $process; private static $db_conn; + private static $auth_account_list = array(); private static $db_queries = array( 'term' => "SELECT DISTINCT user_id FROM courses_users WHERE semester=$1", 'active' => "SELECT DISTINCT user_id FROM courses_users WHERE user_group=1 OR (user_group<>1 AND status=1)", 'clean' => "SELECT DISTINCT user_id FROM courses_users WHERE user_group<>1 AND status<>1" ); + private static $cli = array( + 'term' => function($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"); + }, + 'active' => function($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"); + }, + 'clean' => function($user) { + if (self::$check_auth_account($user)) { + system("/usr/sbin/deluser --quiet {$user} > /dev/null 2>&1"); + } + } + ); public static function main() { //IMPORTANT: This script needs to be run as root! @@ -82,44 +95,25 @@ public static function main() { } //Check CLI args for instructions. Quit if set to false. - $command = cli_args::parse_args(); - if ($command === false) { + if (($cli_args = cli_args::parse_args()) === false) { exit(1); } - - self::$process = array_pad(explode(" ", $command), 2, null); + $command = $cli_args[0]; + $db_params = $cli_args[1]; //Connect to database. Quit on failure. - if (!self::db_conn()) { + if (self::db_conn() === false) { exit(1); } - //Get user list based on type of process. Quit on failure. - $query = self::$db_queries[self::$process[0]]; - $params = array(self::$process[1]); - $result = pg_query_params(self::$db_conn, $query, $params); - if ($result === false) { + //Get user list based on command. Quit on failure. + if (($result = pg_query_params(self::$db_conn, self::$db_queries[$command], $db_params)) === false) { self::log_it("Submitty Auto Account Creation: Cannot read user list from {$db_name}."); exit(1); } - $user_list = pg_fetch_all_columns($result, 0); - - switch(self::$process[0]) { - case 'term': - case 'active': - 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"); - } - break; - case 'clean': - foreach($user_list as $user) { - //deluser - } - break; - default; - exit(1); + while (($user = pg_fetch_result($result, null, 0)) !== false) { + $cli[$command]($user); } } @@ -141,6 +135,25 @@ private static function db_conn() { return true; } + private function check_auth_account($user) { + // This is a validation check for an empty list before searching, so + // might as well wrap in the data loader for when list is empty. + if (empty(self::$auth_account_list)) { + if (($fh = fopen('/etc/passwd', 'r')) === false) { + self::logit("Cannot open '/etc/passwd' to check for auth only accounts."); + exit(1); + } + + while (($row = fgetcsv($fh, 0, ':')) !== false) { + array_push(self::$auth_only_accounts, array('id' => $row[0], 'gecos' => $row[4])); + } + + fclose($fh); + } + + return array_search($user, array_column(self::$auth_only_accounts, 'id')) === false ? false : true; + } + /** * Log message to email and text files * @@ -184,7 +197,7 @@ class cli_args { * Called with 'cli_args::parse_args()' * * @access public - * @return mixed term code as string or boolean false when no term code is present. + * @return array consisting of process command (string) and possibly associated term code (string or null) or boolean false on error. */ public static function parse_args() { self::$args = getopt('t:agrh', array('help')); @@ -195,18 +208,18 @@ public static function parse_args() { self::print_help(); return false; case array_key_exists('a', self::$args): - return "active"; + return array("active", null); case array_key_exists('t', self::$args): - return "term " . self::$args['t']; + return ("term", self::$args['t']); case array_key_exists('g', self::$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) ? "term s{$year}" : (($month >= 8) ? "term f{$year}" : "term u{$year}"); + return ($month <= 5) ? array("term", "s{$year}") : (($month >= 8) ? array("term", "f{$year}") : array("term", "u{$year}")); case array_key_exists('r', self::$args): - return "clean"; + return array("clean", null); default: print self::$help_usage . PHP_EOL; return false; From 94777f28adf794b67f67dac2037e2de62049d25f Mon Sep 17 00:00:00 2001 From: pbailie Date: Thu, 2 Aug 2018 19:26:35 -0400 Subject: [PATCH 03/12] Accounts.php Update pam_accounts/accounts.php WIP --- pam_accounts/accounts.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index a6906f6..8823d70 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -82,7 +82,7 @@ class make_accounts { 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"); }, 'clean' => function($user) { - if (self::$check_auth_account($user)) { + if (self::check_auth_account($user)) { system("/usr/sbin/deluser --quiet {$user} > /dev/null 2>&1"); } } @@ -151,7 +151,8 @@ private function check_auth_account($user) { fclose($fh); } - return array_search($user, array_column(self::$auth_only_accounts, 'id')) === false ? false : true; + $index = array_search($user, array_column(self::$auth_only_accounts, 'id')); + return (self::$auth_only_accounts[$index]['gecos'] === 'auth only account'); } /** From 4de9c85a2404e5a0c220605c9d3f27e29e6b176e Mon Sep 17 00:00:00 2001 From: pbailie Date: Fri, 3 Aug 2018 14:27:21 -0400 Subject: [PATCH 04/12] Accounts.php update pam_accounts/accounts.php WIP --- pam_accounts/accounts.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index 8823d70..52e2526 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -70,9 +70,20 @@ class make_accounts { private static $db_conn; private static $auth_account_list = array(); private static $db_queries = array( - 'term' => "SELECT DISTINCT user_id FROM courses_users WHERE semester=$1", - 'active' => "SELECT DISTINCT user_id FROM courses_users WHERE user_group=1 OR (user_group<>1 AND status=1)", - 'clean' => "SELECT DISTINCT user_id FROM courses_users WHERE user_group<>1 AND status<>1" + 'term' => +"SELECT DISTINCT user_id +FROM courses_users +WHERE semester=$1", + 'active' => +"SELECT DISTINCT user_id +FROM courses_users as cu +LEFT OUTER JOIN courses as c ON cu.course=c.course AND cu.semester=c.semester +WHERE cu.user_group=1 OR (cu.user_group<>1 AND c.status=1)", + 'clean' => +"SELECT DISTINCT user_id +FROM courses_users as cu +LEFT OUTER JOIN courses as c ON cu.course=c.course AND cu.semester=c.semseter +WHERE cu.user_group<>1 AND u.status<>1" ); private static $cli = array( 'term' => function($user) { From bfdd96a9f61d94857bd8917594b39ad89ee543c1 Mon Sep 17 00:00:00 2001 From: pbailie Date: Fri, 3 Aug 2018 19:27:36 -0400 Subject: [PATCH 05/12] Accounts.php update /pam_accounts/accounts.php WIP, refactor --- pam_accounts/accounts.php | 234 ++++++++++++++++++++++++++------------ 1 file changed, 161 insertions(+), 73 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index 52e2526..a6e8a91 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -15,7 +15,7 @@ * * "30 * * * * /var/local/submitty/bin/accounts.php" * - * You may specify the term on the command line with "-t". + * You may specify the term on the command line with "-t [term code]". * "-g" can be used to guess the term by the server's calendar month and year. * "-s" will process accounts based on status and user role. * For example: @@ -62,72 +62,165 @@ date_default_timezone_set('America/New_York'); //Start process -make_accounts::main(); -exit(0); +new make_accounts(); +/** class constructor manages script workflow */ class make_accounts { - private static $db_conn; - private static $auth_account_list = array(); - private static $db_queries = array( - 'term' => -"SELECT DISTINCT user_id -FROM courses_users -WHERE semester=$1", - 'active' => -"SELECT DISTINCT user_id + /** @var resource pgsql database connection */ + private $db_conn; + /** @var string what $workflow to process */ + private $workflow; + /** @var string when the db_query has a paramter, null otherwise */ + private $db_params; + /** @var string DB query to be run */ + private $db_queries; + /** @var closure logic to process $workflow */ + private $system_call; + /** @var array user_id list to be checked against /etc/passwd (only certain workflows) */ + private $auth_account_list; + + 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); + } + + /** + * Initialize class properties, based on $this->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; + } + $this->workflow = $cli_args[0]; + $this->db_params = $cli_args[1]; + + //Define database query based on workflow. + switch($this->workflow) { + case 'term': + $this->db_query = <<db_query = <<1 AND c.status=1)", - 'clean' => -"SELECT DISTINCT user_id +WHERE cu.user_group=1 OR (cu.user_group<>1 AND c.status=1) +SQL; + break; + case 'clean': + $this->db_query = <<1 AND u.status<>1" - ); - private static $cli = array( - 'term' => function($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"); - }, - 'active' => function($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"); - }, - 'clean' => function($user) { - if (self::check_auth_account($user)) { - system("/usr/sbin/deluser --quiet {$user} > /dev/null 2>&1"); - } +WHERE cu.user_group<>1 AND u.status<>1 +SQL; + break; + default: + $this->log_it("Define $this->db_query: Invalid $this->workflow"); + return false; } - ); - public static 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); + //Define system call based on workflow. + switch($this->workflow) { + case 'term': + $this->system_call = function($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"); + }; + break; + case 'active': + $this->system_call = function($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"); + }; + break; + case 'clean': + $this->system_call = function($user) { + if ($this->check_auth_account($user)) { + system("/usr/sbin/deluser --quiet {$user} > /dev/null 2>&1"); + } + }; + break; + default: + $this_>log_it("Define $this->system_call: Invalid $this->workflow"); + return false; } - //Check CLI args for instructions. Quit if set to false. - if (($cli_args = cli_args::parse_args()) === false) { - exit(1); + //Certain workflows require the contents of the passwd file. + switch($this->workflow) { + case 'clean': + $this->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) { + array_push($this->auth_only_accounts, array('id' => $row[0], 'gecos' => $row[4])); + } + + fclose($fh); + break; } - $command = $cli_args[0]; - $db_params = $cli_args[1]; + //Signal success + return true; + } + + /** + * 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 (self::db_conn() === false) { - exit(1); + if ($this->db_conn() === false) { + return false; } //Get user list based on command. Quit on failure. - if (($result = pg_query_params(self::$db_conn, self::$db_queries[$command], $db_params)) === false) { - self::log_it("Submitty Auto Account Creation: Cannot read user list from {$db_name}."); - exit(1); + if (($result = pg_query_params($this->db_conn, $this->db_query, $this->db_params)) === false) { + $this->log_it("Submitty Auto Account Creation: Cannot read user list from {$db_name}."); + return false; } + //Do workflow (iterate through each user returned by database) while (($user = pg_fetch_result($result, null, 0)) !== false) { - $cli[$command]($user); + $this->system_call($user); } + + //Signal success + return true; } + /** + * Establish connection to Submitty Database + * + * @access private + * @return boolean TRUE on success, FALSE when there is a problem. + */ private static function db_conn() { $db_user = DB_LOGIN; $db_pass = DB_PASSWD; @@ -143,35 +236,28 @@ private static function db_conn() { pg_close(self::$db_conn); }); + //Signal success return true; } + /** + * Verify that $user is an "auth only account" + * + * @access private + * @return boolean TRUE when $user is an "auth only account", FALSE otherwise. + */ private function check_auth_account($user) { - // This is a validation check for an empty list before searching, so - // might as well wrap in the data loader for when list is empty. - if (empty(self::$auth_account_list)) { - if (($fh = fopen('/etc/passwd', 'r')) === false) { - self::logit("Cannot open '/etc/passwd' to check for auth only accounts."); - exit(1); - } - - while (($row = fgetcsv($fh, 0, ':')) !== false) { - array_push(self::$auth_only_accounts, array('id' => $row[0], 'gecos' => $row[4])); - } - - fclose($fh); - } - - $index = array_search($user, array_column(self::$auth_only_accounts, 'id')); - return (self::$auth_only_accounts[$index]['gecos'] === 'auth only account'); + $index = array_search($user, array_column($this->auth_only_accounts, 'id')); + return ($this->auth_only_accounts[$index]['gecos'] === 'auth only account'); } /** * Log message to email and text files * + * @access private * @param string $msg */ - private static function log_it($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); @@ -181,11 +267,13 @@ private static function log_it($msg) { } } //END class make_accounts -/** @static class to parse command line arguments */ +/** + * class to parse command line arguments + * + * @static + */ 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] (-a | -t [term code] | -g)" . PHP_EOL; /** @var string short description help message */ @@ -212,25 +300,25 @@ class cli_args { * @return array consisting of process command (string) and possibly associated term code (string or null) or boolean false on error. */ public static function parse_args() { - self::$args = getopt('t:agrh', array('help')); + $args = getopt('t:agrh', array('help')); switch(true) { - case array_key_exists('h', self::$args): - case array_key_exists('help', self::$args): + case array_key_exists('h', $args): + case array_key_exists('help', $args): self::print_help(); return false; - case array_key_exists('a', self::$args): + case array_key_exists('a', $args): return array("active", null); - case array_key_exists('t', self::$args): - return ("term", self::$args['t']); - case array_key_exists('g', self::$args): + 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', self::$args): + case array_key_exists('r', $args): return array("clean", null); default: print self::$help_usage . PHP_EOL; From bb8b5ff4c263af0bfcac93b46a94263406b59cf7 Mon Sep 17 00:00:00 2001 From: pbailie Date: Fri, 3 Aug 2018 19:37:46 -0400 Subject: [PATCH 06/12] Accounts.php update pam_accounts/account.php WIP, comment update --- pam_accounts/accounts.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index a6e8a91..3ebf31f 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -15,9 +15,11 @@ * * "30 * * * * /var/local/submitty/bin/accounts.php" * - * You may specify the term on the command line with "-t [term code]". + * You may specify the term 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. - * "-s" will process accounts based on status and user role. + * "-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 From fd3392045c080db0cbcc681346a97a0e19d1b9a2 Mon Sep 17 00:00:00 2001 From: pbailie Date: Fri, 3 Aug 2018 21:17:11 -0400 Subject: [PATCH 07/12] Accounts.php Update Changes to be committed: modified: pam_accounts/accounts.php WIP, Debugging --- pam_accounts/accounts.php | 49 +++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index 3ebf31f..4427e7c 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -15,7 +15,7 @@ * * "30 * * * * /var/local/submitty/bin/accounts.php" * - * You may specify the term on the command line: + * 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. @@ -33,13 +33,12 @@ 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', 'error.log'); //Where to email error messages so they can get more immediate attention. //Set to null to not send email. @@ -102,6 +101,13 @@ public function __construct() { exit(0); } + public function __destruct() { + //Close DB connection, if it exists. + if (pg_connection_status($this->db_conn) === PGSQL_CONNECTION_OK) { + pg_close($this->db_conn); + } + } + /** * Initialize class properties, based on $this->workflow * @@ -159,8 +165,9 @@ private function init() { break; case 'clean': $this->system_call = function($user) { + print "clean {$user}\n"; if ($this->check_auth_account($user)) { - system("/usr/sbin/deluser --quiet {$user} > /dev/null 2>&1"); + system("/usr/sbin/deluser {$user}"); } }; break; @@ -199,6 +206,7 @@ private function init() { private function process() { //Connect to database. Quit on failure. if ($this->db_conn() === false) { + $this->log_it("Submitty Auto Account Creation: Cannot connect to DB {$db_name}."); return false; } @@ -210,9 +218,10 @@ private function process() { //Do workflow (iterate through each user returned by database) while (($user = pg_fetch_result($result, null, 0)) !== false) { + print "clean {$user}\n"; $this->system_call($user); } - +die; //Signal success return true; } @@ -223,21 +232,16 @@ private function process() { * @access private * @return boolean TRUE on success, FALSE when there is a problem. */ - private static function db_conn() { + private function db_conn() { $db_user = DB_LOGIN; $db_pass = DB_PASSWD; $db_host = DB_HOST; - $db_name = DB_NAME; - self::$db_conn = pg_connect("host={$db_host} dbname={$db_name} user={$db_user} password={$db_pass}"); - if (self::$db_conn === false) { - self::log_it("Submitty Auto Account Creation: Cannot connect to DB {$db_name}."); + $this->db_conn = pg_connect("host={$db_host} dbname=submitty user={$db_user} password={$db_pass} sslmode=prefer"); + if (pg_connection_status($this->db_conn) !== PGSQL_CONNECTION_OK) { + $this->log_it(pg_last_error($this->db_conn)); return false; } - register_shutdown_function(function() { - pg_close(self::$db_conn); - }); - //Signal success return true; } @@ -277,19 +281,20 @@ private function log_it($msg) { class cli_args { /** @var string usage help message */ - private static $help_usage = "Usage: accounts.php [-h | --help] (-a | -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 = << Date: Mon, 6 Aug 2018 18:46:43 -0400 Subject: [PATCH 08/12] Accounts.php Update Changes to be committed: modified: pam_accounts/accounts.php WIP --- pam_accounts/accounts.php | 69 +++++++++++++++------------------------ 1 file changed, 26 insertions(+), 43 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index 4427e7c..eebf01a 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -122,75 +122,58 @@ private function init() { $this->workflow = $cli_args[0]; $this->db_params = $cli_args[1]; - //Define database query based on workflow. + //Define database query AND system call based on workflow. switch($this->workflow) { case 'term': $this->db_query = <<system_call = function($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"); + }; break; + case 'active': $this->db_query = <<1 AND c.status=1) SQL; - break; - case 'clean': - $this->db_query = <<1 AND u.status<>1 -SQL; - break; - default: - $this->log_it("Define $this->db_query: Invalid $this->workflow"); - return false; - } - - //Define system call based on workflow. - switch($this->workflow) { - case 'term': - $this->system_call = function($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"); - }; - break; - case 'active': $this->system_call = function($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"); }; break; - case 'clean': - $this->system_call = function($user) { - print "clean {$user}\n"; - if ($this->check_auth_account($user)) { - system("/usr/sbin/deluser {$user}"); - } - }; - break; - default: - $this_>log_it("Define $this->system_call: Invalid $this->workflow"); - return false; - } - //Certain workflows require the contents of the passwd file. - switch($this->workflow) { case 'clean': - $this->auth_only_accounts = array(); + //'clean' workflow requires contents of the /etc/passwd file. + $this->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) { array_push($this->auth_only_accounts, array('id' => $row[0], 'gecos' => $row[4])); } - fclose($fh); + $this->db_query = <<1 AND c.status<>1 +SQL; + $this->system_call = function($user) { + print "clean {$user}\n"; + if ($this->check_auth_account($user)) { + system("/usr/sbin/deluser {$user}"); + } + }; break; + + default: + $this->log_it("Invalid $this->workflow during init()"); + return false; } //Signal success @@ -205,7 +188,7 @@ private function init() { */ private function process() { //Connect to database. Quit on failure. - if ($this->db_conn() === false) { + if ($this->db_connect() === false) { $this->log_it("Submitty Auto Account Creation: Cannot connect to DB {$db_name}."); return false; } @@ -232,7 +215,7 @@ private function process() { * @access private * @return boolean TRUE on success, FALSE when there is a problem. */ - private function db_conn() { + private function db_connect() { $db_user = DB_LOGIN; $db_pass = DB_PASSWD; $db_host = DB_HOST; From 5bc6fbd0b194ff1bed48ace61a4de61213534d6c Mon Sep 17 00:00:00 2001 From: pbailie Date: Tue, 7 Aug 2018 17:16:46 -0400 Subject: [PATCH 09/12] Accounts.php Update Changes to be committed: modified: pam_accounts/readme.md Updated Readme --- pam_accounts/readme.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/pam_accounts/readme.md b/pam_accounts/readme.md index deda894..e7e3f0a 100644 --- a/pam_accounts/readme.md +++ b/pam_accounts/readme.md @@ -1,15 +1,26 @@ # 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. +- `-g` Guess the term code, based on calendar month and year, to create +authentication accounts. +- `-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,7 +36,7 @@ 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). From 2a25d7642c8ac53647cf9f4efa1eb625dc2c5dac Mon Sep 17 00:00:00 2001 From: pbailie Date: Tue, 7 Aug 2018 17:21:06 -0400 Subject: [PATCH 10/12] Accounts.php Update Changes to be committed: modified: pam_accounts/readme.md Readme correction --- pam_accounts/readme.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/pam_accounts/readme.md b/pam_accounts/readme.md index e7e3f0a..d33a8a4 100644 --- a/pam_accounts/readme.md +++ b/pam_accounts/readme.md @@ -40,8 +40,6 @@ will follow the pattern of TYY, where - 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. From c462017f9861005762d6ca05222334280adf7f70 Mon Sep 17 00:00:00 2001 From: pbailie Date: Wed, 8 Aug 2018 20:00:17 -0400 Subject: [PATCH 11/12] Accouns.php Update Changes to be committed: modified: pam_accounts/accounts.php modified: pam_accounts/readme.md Pull request candidate --- pam_accounts/accounts.php | 119 ++++++++++++++++++++------------------ pam_accounts/readme.md | 5 +- 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index eebf01a..a1f91af 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -29,20 +29,23 @@ * @author Peter Bailie, Systems Programmer (RPI dept of computer science) */ -error_reporting(0); -ini_set('display_errors', 0); +error_reporting(E_ALL); +ini_set('display_errors', 'stderr'); //Database access define('DB_LOGIN', 'submitty_dbuser'); -define('DB_PASSWD', 'submitty_dbuser_pa55W0rd'); +//define('DB_PASSWD', 'submitty_dbuser_pa55W0rd'); +define('DB_PASSWD', 'submitty_dbuser'); define('DB_HOST', 'localhost'); //Location of accounts creation error log file -define('ERROR_LOG_FILE', 'error.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'); +//define('ERROR_EMAIL', 'sysadmins@lists.myuniversity.edu'); +define('ERROR_EMAIL', null); + /* SUGGESTED SETTINGS FOR TIMEZONES IN USA ------------------------------------- * @@ -69,17 +72,17 @@ class make_accounts { /** @var resource pgsql database connection */ - private $db_conn; - /** @var string what $workflow to process */ - private $workflow; - /** @var string when the db_query has a paramter, null otherwise */ - private $db_params; + private static $db_conn; + /** @var string what workflow to process */ + private static $workflow; + /** @var array paramater list for DB query */ + private static $db_params; /** @var string DB query to be run */ - private $db_queries; - /** @var closure logic to process $workflow */ - private $system_call; - /** @var array user_id list to be checked against /etc/passwd (only certain workflows) */ - private $auth_account_list; + private static $db_query; + /** @var string function to call to process $workflow */ + private static $workflow_function; + /** @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! @@ -103,13 +106,13 @@ public function __construct() { public function __destruct() { //Close DB connection, if it exists. - if (pg_connection_status($this->db_conn) === PGSQL_CONNECTION_OK) { - pg_close($this->db_conn); + if (pg_connection_status(self::$db_conn) === PGSQL_CONNECTION_OK) { + pg_close(self::$db_conn); } } /** - * Initialize class properties, based on $this->workflow + * Initialize class properties, based on self::$workflow * * @access private * @return boolean TRUE on success, FALSE when there is a problem. @@ -119,60 +122,54 @@ private function init() { if (($cli_args = cli_args::parse_args()) === false) { return false; } - $this->workflow = $cli_args[0]; - $this->db_params = $cli_args[1]; + 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($this->workflow) { + switch(self::$workflow) { case 'term': - $this->db_query = <<system_call = function($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"); - }; + self::$workflow_function = 'add_user'; break; case 'active': - $this->db_query = <<1 AND c.status=1) SQL; - $this->system_call = function($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"); - }; + self::$workflow_function = 'add_user'; break; case 'clean': - //'clean' workflow requires contents of the /etc/passwd file. - $this->auth_only_accounts = array(); + //'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) { - array_push($this->auth_only_accounts, array('id' => $row[0], 'gecos' => $row[4])); + if (strpos($row[4], 'auth only account') !== false) { + self::$auth_only_accounts[] = $row[0]; + } } fclose($fh); - $this->db_query = <<1 AND c.status<>1 SQL; - $this->system_call = function($user) { - print "clean {$user}\n"; - if ($this->check_auth_account($user)) { - system("/usr/sbin/deluser {$user}"); - } - }; + self::$workflow_function = 'remove_user'; break; default: - $this->log_it("Invalid $this->workflow during init()"); + $this->log_it("Invalid self::$workflow during init()"); return false; } @@ -194,17 +191,17 @@ private function process() { } //Get user list based on command. Quit on failure. - if (($result = pg_query_params($this->db_conn, $this->db_query, $this->db_params)) === false) { + 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; } - //Do workflow (iterate through each user returned by database) - while (($user = pg_fetch_result($result, null, 0)) !== false) { - print "clean {$user}\n"; - $this->system_call($user); + $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); } -die; + //Signal success return true; } @@ -219,9 +216,9 @@ private function db_connect() { $db_user = DB_LOGIN; $db_pass = DB_PASSWD; $db_host = DB_HOST; - $this->db_conn = pg_connect("host={$db_host} dbname=submitty user={$db_user} password={$db_pass} sslmode=prefer"); - if (pg_connection_status($this->db_conn) !== PGSQL_CONNECTION_OK) { - $this->log_it(pg_last_error($this->db_conn)); + 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; } @@ -230,14 +227,26 @@ private function db_connect() { } /** - * Verify that $user is an "auth only account" + * Add a user for authentication with PAM. * * @access private - * @return boolean TRUE when $user is an "auth only account", FALSE otherwise. + * @param string $user User ID to added. */ - private function check_auth_account($user) { - $index = array_search($user, array_column($this->auth_only_accounts, 'id')); - return ($this->auth_only_accounts[$index]['gecos'] === 'auth only account'); + 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"); + } + + /** + * 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"); + } } /** diff --git a/pam_accounts/readme.md b/pam_accounts/readme.md index d33a8a4..d026f1c 100644 --- a/pam_accounts/readme.md +++ b/pam_accounts/readme.md @@ -15,9 +15,10 @@ 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. +- `-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. +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. From ff3a39c48b57f249c6c4c7ec401ea59484e449c8 Mon Sep 17 00:00:00 2001 From: pbailie Date: Thu, 9 Aug 2018 15:21:32 -0400 Subject: [PATCH 12/12] Accounts.php update Changes to be committed: modified: pam_accounts/accounts.php Last minute tweaks to comments and config template. --- pam_accounts/accounts.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/pam_accounts/accounts.php b/pam_accounts/accounts.php index a1f91af..4000f7e 100644 --- a/pam_accounts/accounts.php +++ b/pam_accounts/accounts.php @@ -29,13 +29,12 @@ * @author Peter Bailie, Systems Programmer (RPI dept of computer science) */ -error_reporting(E_ALL); -ini_set('display_errors', 'stderr'); +error_reporting(null); +ini_set('display_errors', '0'); //Database access define('DB_LOGIN', 'submitty_dbuser'); -//define('DB_PASSWD', 'submitty_dbuser_pa55W0rd'); -define('DB_PASSWD', 'submitty_dbuser'); +define('DB_PASSWD', 'submitty_dbuser_pa55W0rd'); define('DB_HOST', 'localhost'); //Location of accounts creation error log file @@ -43,8 +42,7 @@ //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'); -define('ERROR_EMAIL', null); +define('ERROR_EMAIL', 'sysadmins@lists.myuniversity.edu'); /* SUGGESTED SETTINGS FOR TIMEZONES IN USA ------------------------------------- @@ -68,20 +66,20 @@ //Start process new make_accounts(); -/** class constructor manages script workflow */ +/** Class constructor manages script workflow */ class make_accounts { - /** @var resource pgsql database connection */ + /** @static @var resource pgsql database connection */ private static $db_conn; - /** @var string what workflow to process */ + /** @static @var string what workflow to process */ private static $workflow; - /** @var array paramater list for DB query */ + /** @static @var array paramater list for DB query */ private static $db_params; - /** @var string DB query to be run */ + /** @static @var string DB query to be run */ private static $db_query; - /** @var string function to call to process $workflow */ + /** @static @var string function to call to process $workflow */ private static $workflow_function; - /** @var array user_id list of 'auth only accounts', read from /etc/passwd */ + /** @static @var array user_id list of 'auth only accounts', read from /etc/passwd */ private static $auth_only_accounts; public function __construct() {