first stable

This commit is contained in:
Alexander Zhirov 2024-12-06 02:28:00 +03:00
parent da7e160b9f
commit bb55ad76e3
16 changed files with 409 additions and 1 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
bin
.vscode

View File

@ -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 /<path-to>/chkpass
chmod 4755 /<path-to>/chkpass
```

2
chkpass.conf Normal file
View File

@ -0,0 +1,2 @@
check = login
change = passwd

22
dub.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "chkpass",
"authors": [
"Alexander Zhirov <alexander@zhirov.kz>"
],
"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"
]
}

10
dub.selections.json Normal file
View File

@ -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"
}
}

4
install-debian-dependencies.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
# apt install -y libpam-dev
# eopkg it -y pam-devel

76
source/app.d Normal file
View File

@ -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;
}

View File

@ -0,0 +1,4 @@
module chkpass.libpam;
public import chkpass.libpam.pam;
public import chkpass.libpam.types;

View File

@ -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);

View File

@ -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;

6
source/chkpass/package.d Normal file
View File

@ -0,0 +1,6 @@
module chkpass;
public import chkpass.libpam;
public import chkpass.user;
public import chkpass.utils;
public import chkpass.version_;

164
source/chkpass/user/auth.d Normal file
View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
module chkpass.user;
public import chkpass.user.auth;

View File

@ -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;
}

View File

@ -0,0 +1,3 @@
module chkpass.utils;
public import chkpass.utils.config;

View File

@ -0,0 +1,3 @@
module chkpass.version_;
enum chkpassVersion = "0.1.0";