diff --git a/lib/util.c b/lib/util.c index 8a7b66b..59d5530 100644 --- a/lib/util.c +++ b/lib/util.c @@ -24,6 +24,7 @@ #include "groupaccess.h" int duo_debug = 0; +int duo_quiet = 0; void duo_config_default(struct duo_config *cfg) diff --git a/lib/util.h b/lib/util.h index 8a16ac3..599e739 100644 --- a/lib/util.h +++ b/lib/util.h @@ -18,6 +18,7 @@ #include extern int duo_debug; +extern int duo_quiet; enum { DUO_FAIL_SAFE = 0, diff --git a/pam_duo/pam_duo.8 b/pam_duo/pam_duo.8 index 31e956a..af7bbb5 100644 --- a/pam_duo/pam_duo.8 +++ b/pam_duo/pam_duo.8 @@ -21,6 +21,9 @@ Specify an alternate configuration file to load. Default is .It debug Debug mode; send log messages to stderr instead of syslog. .El +.It quiet +Quiet mode; don't print success/failure messages. +.El .Sh CONFIGURATION The INI-format configuration file must have a .Dq Li duo diff --git a/pam_duo/pam_duo.c b/pam_duo/pam_duo.c index d3a2aa0..785919f 100644 --- a/pam_duo/pam_duo.c +++ b/pam_duo/pam_duo.c @@ -88,7 +88,9 @@ __ini_handler(void *u, const char *section, const char *name, const char *val) static void __duo_status(void *arg, const char *msg) { - pam_info((pam_handle_t *)arg, "%s", msg); + if (!duo_quiet) { + pam_info((pam_handle_t *)arg, "%s", msg); + } } static char * diff --git a/pam_duo/pam_duo_private.c b/pam_duo/pam_duo_private.c index 4d766e3..3237175 100644 --- a/pam_duo/pam_duo_private.c +++ b/pam_duo/pam_duo_private.c @@ -19,6 +19,9 @@ parse_argv(const char **config, int argc, const char *argv[]) } else if (strcmp("debug", argv[i]) == 0) { /* duo_debug is a global variable defined in util.h */ duo_debug = 1; + } else if (strcmp("quiet", argv[i]) == 0) { + /* duo_quiet is a global variable defined in util.h */ + duo_quiet = 1; } else { duo_syslog(LOG_ERR, "Invalid pam_duo option: '%s'", argv[i]); diff --git a/pam_duo/pam_duo_private.h b/pam_duo/pam_duo_private.h index f06077e..3c6905c 100644 --- a/pam_duo/pam_duo_private.h +++ b/pam_duo/pam_duo_private.h @@ -11,6 +11,6 @@ #include #include "util.h" -/* Parses argv to get the configuration file location and the debug mode */ +/* Parses argv to get the configuration file location and the debug/quiet modes */ int parse_argv(const char **config, int argc, const char *argv[]); diff --git a/tests/test_pam_duo.py b/tests/test_pam_duo.py index 0347597..156f380 100755 --- a/tests/test_pam_duo.py +++ b/tests/test_pam_duo.py @@ -244,6 +244,20 @@ def test_max_prompts_equals_maximum(self): ) +@unittest.skipIf(sys.platform == "sunos5", SOLARIS_ISSUE) +class TestPamQuiet(unittest.TestCase): + def run(self, result=None): + with MockDuo(NORMAL_CERT): + return super(TestPamQuiet, self).run(result) + + def test_quiet(self): + with TempConfig(MOCKDUO_PROMPTS_1) as temp: + result = pam_duo(["-d", "-f", "pam_prompt", "-c", temp.name, "-q", "true"]) + self.assertRegex(result["stderr"][0], "Failed Duo login for 'pam_prompt'") + self.assertEqual(len(result["stdout"]), 1) + self.assertRegex(result["stdout"][0], "^$") + + @unittest.skipIf(sys.platform == "sunos5", SOLARIS_ISSUE) class TestPamEnv(CommonSuites.Env): def call_binary(self, *args, **kwargs): @@ -323,6 +337,21 @@ def test_invalid_argument(self): self.assertEqual(process.returncode, 1) +@unittest.skipIf(sys.platform == "sunos5", SOLARIS_ISSUE) +class TestPamdConfQuiet(unittest.TestCase): + def test_quiet_argument(self): + with TempConfig(MOCKDUO_CONF) as duo_config: + pamd_conf = "auth required {libpath}/pam_duo.so conf={duo_config_path} quiet".format( + libpath=os.path.join(topbuilddir, "pam_duo", ".libs"), + duo_config_path=duo_config.name, + ) + with TempPamConfig(pamd_conf) as pam_config: + process = testpam( + ["-d", "-c", duo_config.name, "-f", "whatever"], pam_config.name + ) + self.assertEqual(process.returncode, 0) + + @unittest.skipIf(sys.platform == "sunos5", SOLARIS_ISSUE) class TestPamGECOS(CommonTestCase): def run(self, result=None): diff --git a/tests/testpam.py b/tests/testpam.py index 0b2ce32..52fb95b 100755 --- a/tests/testpam.py +++ b/tests/testpam.py @@ -79,13 +79,14 @@ def testpam(args, config_file_name, env_overrides=None): def main(): try: - opts, args = getopt.getopt(sys.argv[1:], "dc:f:h:") + opts, args = getopt.getopt(sys.argv[1:], "dqc:f:h:") except getopt.GetoptError: usage() opt_conf = "/etc/duo/pam_duo.conf" opt_user = getpass.getuser() opt_host = None + opt_quiet = False for o, a in opts: if o == "-c": @@ -94,6 +95,8 @@ def main(): opt_user = a elif o == "-h": opt_host = a + elif o == "-q": + opt_quiet = True args = [opt_user] if opt_host: @@ -102,6 +105,8 @@ def main(): config = "auth required {libpath}/pam_duo.so conf={duo_config_path} debug".format( libpath=paths.topbuilddir + "/pam_duo/.libs", duo_config_path=opt_conf ) + if opt_quiet: + config = config + " quiet" with TempPamConfig(config) as config_file: process = testpam(args, config_file.name)