From bb55ad76e33a2e724341a4084bb9211eda3a4f9a Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Fri, 6 Dec 2024 02:28:00 +0300 Subject: [PATCH] first stable --- .gitignore | 2 + README.md | 9 +- chkpass.conf | 2 + dub.json | 22 +++++ dub.selections.json | 10 ++ install-debian-dependencies.sh | 4 + source/app.d | 76 +++++++++++++++ source/chkpass/libpam/package.d | 4 + source/chkpass/libpam/pam.d | 11 +++ source/chkpass/libpam/types.d | 61 ++++++++++++ source/chkpass/package.d | 6 ++ source/chkpass/user/auth.d | 164 ++++++++++++++++++++++++++++++++ source/chkpass/user/package.d | 3 + source/chkpass/utils/config.d | 30 ++++++ source/chkpass/utils/package.d | 3 + source/chkpass/version_.d | 3 + 16 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 chkpass.conf create mode 100644 dub.json create mode 100644 dub.selections.json create mode 100755 install-debian-dependencies.sh create mode 100644 source/app.d create mode 100644 source/chkpass/libpam/package.d create mode 100644 source/chkpass/libpam/pam.d create mode 100644 source/chkpass/libpam/types.d create mode 100644 source/chkpass/package.d create mode 100644 source/chkpass/user/auth.d create mode 100644 source/chkpass/user/package.d create mode 100644 source/chkpass/utils/config.d create mode 100644 source/chkpass/utils/package.d create mode 100644 source/chkpass/version_.d diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..acf56c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +.vscode diff --git a/README.md b/README.md index 046a8c6..7bc22e4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # CHecKPASSword -Проверка и смена пароля пользователя с использованием библиотеки PAM +Checking and changing the user's password using the PAM library and its modules + +To use it, you need to modify the binary file: + +``` +chown root:root //chkpass +chmod 4755 //chkpass +``` diff --git a/chkpass.conf b/chkpass.conf new file mode 100644 index 0000000..9641d2a --- /dev/null +++ b/chkpass.conf @@ -0,0 +1,2 @@ +check = login +change = passwd diff --git a/dub.json b/dub.json new file mode 100644 index 0000000..6a166c5 --- /dev/null +++ b/dub.json @@ -0,0 +1,22 @@ +{ + "name": "chkpass", + "authors": [ + "Alexander Zhirov " + ], + "license": "GPL-2.0", + "copyright": "© Alexander Zhirov, 2024", + "description": "Checking and changing the user's password using the PAM library and its modules", + "targetPath": "bin", + "targetType": "executable", + "dependencies": { + "singlog": "~>0.5.0", + "readconf": "~>0.4.1", + "commandr": "~>1.1.0" + }, + "libs": [ + "pam" + ], + "preBuildCommands": [ + "./install-debian-dependencies.sh" + ] +} diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..7d7b4d7 --- /dev/null +++ b/dub.selections.json @@ -0,0 +1,10 @@ +{ + "fileVersion": 1, + "versions": { + "commandr": "1.1.0", + "datefmt": "1.0.4", + "readconf": "0.4.1", + "silly": "1.2.0-dev.2", + "singlog": "0.5.0" + } +} diff --git a/install-debian-dependencies.sh b/install-debian-dependencies.sh new file mode 100755 index 0000000..d7a7782 --- /dev/null +++ b/install-debian-dependencies.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# apt install -y libpam-dev +# eopkg it -y pam-devel diff --git a/source/app.d b/source/app.d new file mode 100644 index 0000000..ac429bb --- /dev/null +++ b/source/app.d @@ -0,0 +1,76 @@ +import chkpass; + +import commandr; +import singlog; +import core.stdc.stdlib : EXIT_SUCCESS, EXIT_FAILURE; +import std.stdio : writeln; + +private string programName = "chkpass"; + +int main(string[] args) { + auto argumets = new Program(programName, chkpassVersion) + .add(new Command("check", "check user password") + .add(new Option("m", "module", "use a dedicated PAM module") + .optional) + .add(new Argument("username") + .required) + .add(new Argument("password") + .required)) + .add(new Command("change", "change user password") + .add(new Option("m", "module", "use a dedicated PAM module") + .optional) + .add(new Argument("username") + .required) + .add(new Argument("password") + .required) + .add(new Argument("new-password") + .required)) + .parse(args); + + string pamod, user, password, newPassword, command; + + argumets + .on("check", (a) { + command = a.name; + pamod = a.option("module"); + user = a.arg("username"); + password = a.arg("password"); + }) + .on("change", (a) { + command = a.name; + pamod = a.option("module"); + user = a.arg("username"); + password = a.arg("password"); + newPassword = a.arg("new-password"); + }); + + log.output(log.output.syslog) + .program(programName) + .level(log.level.error); + + string configFile = "/etc/chkpass/chkpass.conf"; + auto settings = readConfigFile(configFile); + + auto auth = new Auth; + + final switch(command) { + case "check": + if (!pamod.length) pamod = settings.check; + if (auth.authenticate(pamod, user, password)) { + writeln("Password verification failed"); + return EXIT_FAILURE; + } + writeln("Password verification successful"); + break; + case "change": + if (!pamod.length) pamod = settings.change; + if (auth.changePassword(pamod, user, password, newPassword)) { + writeln("The password has not been changed"); + return EXIT_FAILURE; + } + writeln("The password was successfully changed"); + break; + } + + return EXIT_SUCCESS; +} diff --git a/source/chkpass/libpam/package.d b/source/chkpass/libpam/package.d new file mode 100644 index 0000000..0d12a0f --- /dev/null +++ b/source/chkpass/libpam/package.d @@ -0,0 +1,4 @@ +module chkpass.libpam; + +public import chkpass.libpam.pam; +public import chkpass.libpam.types; diff --git a/source/chkpass/libpam/pam.d b/source/chkpass/libpam/pam.d new file mode 100644 index 0000000..29bfbe5 --- /dev/null +++ b/source/chkpass/libpam/pam.d @@ -0,0 +1,11 @@ +module chkpass.libpam.pam; + +import chkpass.libpam.types; + +extern(C): + +int pam_start(const(char) *service_name, const(char) *user, const PamConv *pam_conversation, PamConv **pamh); +int pam_authenticate(PamConv *pamh, int flags); +int pam_end(PamConv *pamh, int pam_status); +int pam_chauthtok(PamConv *pamh, int flags); +int pam_acct_mgmt(PamConv *pamh, int flags); diff --git a/source/chkpass/libpam/types.d b/source/chkpass/libpam/types.d new file mode 100644 index 0000000..95d671c --- /dev/null +++ b/source/chkpass/libpam/types.d @@ -0,0 +1,61 @@ +module chkpass.libpam.types; + +extern(C): + +struct PamMessage { + int msg_style; + const(char) *msg; +} + +struct PamResponse { + char *resp; + int resp_retcode; +} + +alias conversation = int function(int num_msg, const PamMessage **msg, PamResponse **resp, void *appdata_ptr); + +struct PamConv { + conversation *conv; + void *appdata_ptr; +} + +const (char) *pam_strerror(PamConv *pamh, int errnum); + +enum PAM_SUCCESS = 0; +enum PAM_OPEN_ERR = 1; +enum PAM_SYMBOL_ERR = 2; +enum PAM_SERVICE_ERR = 3; +enum PAM_SYSTEM_ERR = 4; +enum PAM_BUF_ERR = 5; +enum PAM_PERM_DENIED = 6; +enum PAM_AUTH_ERR = 7; +enum PAM_CRED_INSUFFICIENT = 8; +enum PAM_AUTHINFO_UNAVAIL = 9; +enum PAM_USER_UNKNOWN = 10; +enum PAM_MAXTRIES = 11; +enum PAM_NEW_AUTHTOK_REQD = 12; +enum PAM_ACCT_EXPIRED = 13; +enum PAM_SESSION_ERR = 14; +enum PAM_CRED_UNAVAIL = 15; +enum PAM_CRED_EXPIRED = 16; +enum PAM_CRED_ERR = 17; +enum PAM_NO_MODULE_DATA = 18; +enum PAM_CONV_ERR = 19; +enum PAM_AUTHTOK_ERR = 20; +enum PAM_AUTHTOK_RECOVERY_ERR = 21; +enum PAM_AUTHTOK_LOCK_BUSY = 22; +enum PAM_AUTHTOK_DISABLE_AGING = 23; +enum PAM_TRY_AGAIN = 24; +enum PAM_IGNORE = 25; +enum PAM_ABORT = 26; +enum PAM_AUTHTOK_EXPIRED = 27; +enum PAM_MODULE_UNKNOWN = 28; +enum PAM_BAD_ITEM = 29; +enum PAM_CONV_AGAIN = 30; +enum PAM_INCOMPLETE = 31; +enum _PAM_RETURN_VALUES = 32; + +enum PAM_PROMPT_ECHO_OFF = 1; +enum PAM_PROMPT_ECHO_ON = 2; +enum PAM_ERROR_MSG = 3; +enum PAM_TEXT_INFO = 4; diff --git a/source/chkpass/package.d b/source/chkpass/package.d new file mode 100644 index 0000000..9686cb5 --- /dev/null +++ b/source/chkpass/package.d @@ -0,0 +1,6 @@ +module chkpass; + +public import chkpass.libpam; +public import chkpass.user; +public import chkpass.utils; +public import chkpass.version_; diff --git a/source/chkpass/user/auth.d b/source/chkpass/user/auth.d new file mode 100644 index 0000000..c0f8a40 --- /dev/null +++ b/source/chkpass/user/auth.d @@ -0,0 +1,164 @@ +module chkpass.user.auth; + +import chkpass.libpam; + +import std.string : toStringz; +import core.stdc.stdlib; +import std.conv; +import core.stdc.string : strdup, strstr; +import std.format; + +import singlog; + +enum { + AUTH_SUCCESS = 0, + AUTH_ERR_USER = 1, + AUTH_ERR_PASS = 2, + AUTH_ERR_NPASS = 3, + AUTH_ERR_START = 4, + AUTH_ERR_AUTH = 5, + AUTH_ERR_ACCT = 6, + AUTH_ERR_CHTOK = 7, + AUTH_ERR_END = 8 +} + +private: + +struct PAMdata { + string password; + string newPassword; +} + +extern(C) int conversation_func(int num_msg, const PamMessage **msg, PamResponse **resp, void *appdata_ptr) { + PAMdata *data = cast(PAMdata*)appdata_ptr; + + PamResponse *responses = cast(PamResponse *)calloc(num_msg, PamResponse.sizeof); + + if (responses == null) { + return PAM_BUF_ERR; + } + + for (int count = 0; count < num_msg; ++count) { + responses[count].resp_retcode = 0; + switch (msg[count].msg_style) { + case PAM_PROMPT_ECHO_ON: + case PAM_PROMPT_ECHO_OFF: + switch (msg[count].msg.to!string) { + case "New password: ": + case "Retype new password: ": + responses[count].resp = strdup(data.newPassword.toStringz); + break; + case "Password: ": + case "Current password: ": + responses[count].resp = strdup(data.password.toStringz); + break; + default: + responses[count].resp = null; + break; + } + break; + default: + responses[count].resp = null; + break; + } + } + + *resp = responses; + + return PAM_SUCCESS; +} + +public: + +class Auth { + int authenticate(string pamod, string username, string password) { + if (!username.length) { + log.e("%s:%d: Username cannot be empty".format(__FUNCTION__, __LINE__)); + return AUTH_ERR_USER; + } + + if (!password.length) { + log.e("%s:%d: Password cannot be empty".format(__FUNCTION__, __LINE__)); + return AUTH_ERR_PASS; + } + + PamConv *pamh = null; + int retval = 0; + + PAMdata data = { password }; + void *appdata_ptr = &data; + + PamConv conv = { cast(conversation*)&conversation_func, appdata_ptr }; + + retval = pam_start(pamod.toStringz, username.toStringz, &conv, &pamh); + if (retval != PAM_SUCCESS) { + log.e("%s:%d: %s".format(__FUNCTION__, __LINE__, pam_strerror(pamh, retval).to!string)); + return AUTH_ERR_START; + } + + retval = pam_authenticate(pamh, 0); + if (retval != PAM_SUCCESS) { + log.e("%s:%d: %s".format(__FUNCTION__, __LINE__, pam_strerror(pamh, retval).to!string)); + pam_end(pamh, retval); + return AUTH_ERR_AUTH; + } + + retval = pam_end(pamh, PAM_SUCCESS); + if (retval != PAM_SUCCESS) { + log.e("%s:%d: %s".format(__FUNCTION__, __LINE__, pam_strerror(pamh, retval).to!string)); + return AUTH_ERR_END; + } + + return AUTH_SUCCESS; + } + + int changePassword(string pamod, string username, string password, string newPassword) { + if (!username.length) { + return AUTH_ERR_USER; + } + + if (!password.length) { + return AUTH_ERR_PASS; + } + + if (!newPassword.length) { + return AUTH_ERR_NPASS; + } + + PamConv *pamh = null; + int retval = 0; + + PAMdata data = { password, newPassword }; + void *appdata_ptr = &data; + + PamConv conv = { cast(conversation*)&conversation_func, appdata_ptr }; + + retval = pam_start(pamod.toStringz, username.toStringz, &conv, &pamh); + if (retval != PAM_SUCCESS) { + log.e("%s:%d: %s".format(__FUNCTION__, __LINE__, pam_strerror(pamh, retval).to!string)); + return AUTH_ERR_START; + } + + retval = pam_acct_mgmt(pamh, 0); + if (retval != PAM_SUCCESS) { + log.e("%s:%d: %s".format(__FUNCTION__, __LINE__, pam_strerror(pamh, retval).to!string)); + pam_end(pamh, retval); + return AUTH_ERR_ACCT; + } + + retval = pam_chauthtok(pamh, 0); + if (retval != PAM_SUCCESS) { + log.e("%s:%d: %s".format(__FUNCTION__, __LINE__, pam_strerror(pamh, retval).to!string)); + pam_end(pamh, retval); + return AUTH_ERR_CHTOK; + } + + retval = pam_end(pamh, PAM_SUCCESS); + if (retval != PAM_SUCCESS) { + log.e("%s:%d: %s".format(__FUNCTION__, __LINE__, pam_strerror(pamh, retval).to!string)); + return AUTH_ERR_END; + } + + return AUTH_SUCCESS; + } +} diff --git a/source/chkpass/user/package.d b/source/chkpass/user/package.d new file mode 100644 index 0000000..f7582b3 --- /dev/null +++ b/source/chkpass/user/package.d @@ -0,0 +1,3 @@ +module chkpass.user; + +public import chkpass.user.auth; \ No newline at end of file diff --git a/source/chkpass/utils/config.d b/source/chkpass/utils/config.d new file mode 100644 index 0000000..6600444 --- /dev/null +++ b/source/chkpass/utils/config.d @@ -0,0 +1,30 @@ +module chkpass.utils.config; + +import std.file; +import readconf; +import singlog; + +struct Settings { + string check = "login"; + string change = "passwd"; +} + +Settings readConfigFile(string configFile) { + Settings settings; + + if (configFile.exists) { + rc.read(configFile); + + ConfigSection mainSection; + + try { + mainSection = rc[][]; + if (!mainSection.key("check").empty) settings.check = mainSection.key("check"); + if (!mainSection.key("change").empty) settings.change = mainSection.key("change"); + } catch (Exception e) { + log.w("An error occurred while reading the configuration file"); + } + } + + return settings; +} diff --git a/source/chkpass/utils/package.d b/source/chkpass/utils/package.d new file mode 100644 index 0000000..8db4b8c --- /dev/null +++ b/source/chkpass/utils/package.d @@ -0,0 +1,3 @@ +module chkpass.utils; + +public import chkpass.utils.config; diff --git a/source/chkpass/version_.d b/source/chkpass/version_.d new file mode 100644 index 0000000..fde0e5b --- /dev/null +++ b/source/chkpass/version_.d @@ -0,0 +1,3 @@ +module chkpass.version_; + +enum chkpassVersion = "0.1.0";