Skip to content

Commit 4ce9c8e

Browse files
committed
Initial commit
0 parents  commit 4ce9c8e

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pam_split_token.o
2+
pam_split_token.so

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright © 2004-2023 Basefarm AS all rights reserved
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Variables
2+
TARGET = pam_split_token.so
3+
OBJS = pam_split_token.o
4+
CFLAGS = -fPIC -Wall
5+
LDFLAGS = -shared
6+
LIBS = -lpam
7+
8+
# Default target
9+
all: $(TARGET)
10+
11+
# Compile the object file
12+
%.o: %.c
13+
$(CC) $(CFLAGS) -c $< -o $@
14+
15+
# Link the shared library
16+
$(TARGET): $(OBJS)
17+
$(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
18+
19+
# Clean up the build files
20+
clean:
21+
rm -f $(OBJS) $(TARGET)

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# pam_split_token
2+
3+
The purpose of this module is to simply split of an OTP token from the password received. It strips of the last "+" and following chars.
4+
5+
The `PAM_AUTHTOK` is modified to be only the first part of the received token (password) and it sets the environnment variable `PAM_SPLIT_TOKEN` to the chars following the + sign. The PAM_SPLIT_TOKEN can the be accessed with pam_getenv. The pam_exec.so module does this, but the pam_script.so module does not.
6+
7+
This was you can for example use this as backend element for Apache basic auth using mod_authnz_pam.
8+
9+
## Requirements
10+
11+
You must pass the parameter `forward_pass` to the module, so that it's allowed to pass on the modified `PAM_AUTHTOK`.
12+
13+
## options
14+
15+
If `PAM_AUTHTOK` is not set you can let the module ask for it if you provide the argument `query_missing_token`.
16+
This way you can use it as the first module in an auth stack in PAM, eg. for SSH login.
17+
18+
## Examples
19+
20+
This is how you might use the module to receive the password+token combo in /etc/pam-d/sshd
21+
22+
```pam
23+
auth required pam_split_token.so forward_pass query_missing_token
24+
auth required pam_exec.so /usr/share/libpam-script/pam_script_auth
25+
@include common-auth
26+
```
27+
28+
If the password is already provided, eg. in /etc/pam.d/apache you don't to query for the token,
29+
as this could lead to errors.
30+
31+
```pam
32+
auth required pam_split_token.so forward_pass
33+
auth required pam_exec.so /usr/share/libpam-script/pam_script_auth
34+
@include common-auth
35+
```
36+
37+
The pam_script_auth could then contain something like:
38+
```bash
39+
#!/bin/bash
40+
41+
if [[ $PAM_SPLIT_TOKEN == "123456" ]]; then
42+
exit 0
43+
fi
44+
echo "Bad OTP: $PAM_SPLIT_TOKEN"
45+
exit 1
46+
```
47+
48+
This would succeed if the password `Sup3rS3cre7+123456` was provided.
49+
50+
2024-09-04: bahner

pam_split_token.c

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#include <security/pam_modules.h>
2+
#include <security/pam_ext.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
#include <stdio.h>
6+
#include <syslog.h>
7+
8+
/*
9+
* Helper function to check if a specific argument is provided in pam.conf
10+
*/
11+
int has_argument(int argc, const char **argv, const char *arg) {
12+
for (int i = 0; i < argc; i++) {
13+
if (strcmp(argv[i], arg) == 0) {
14+
return 1; // Argument found
15+
}
16+
}
17+
return 0; // Argument not found
18+
}
19+
20+
/*
21+
* PAM authentication function.
22+
*/
23+
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) {
24+
const char *authtok;
25+
int pam_ret;
26+
27+
// Check if the "query_missing_token" argument is provided
28+
int query_missing_token = has_argument(argc, argv, "query_missing_token");
29+
30+
// Retrieve the current PAM_AUTHTOK (the user-supplied password + token)
31+
pam_ret = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&authtok);
32+
if (pam_ret != PAM_SUCCESS || authtok == NULL || strlen(authtok) == 0) {
33+
// Only prompt if query_missing_token argument is provided
34+
if (query_missing_token) {
35+
// Allocate a buffer for the input
36+
char *input = NULL;
37+
38+
// Prompt the user for the password+token
39+
pam_ret = pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, &input, "Password+Token: ");
40+
if (pam_ret != PAM_SUCCESS || input == NULL) {
41+
pam_syslog(pamh, LOG_ERR, "Failed to prompt for Password+Token.");
42+
return PAM_AUTH_ERR;
43+
}
44+
45+
authtok = input; // Use this as the authtok
46+
} else {
47+
pam_syslog(pamh, LOG_ERR, "PAM_AUTHTOK is empty and query_missing_token is not set.");
48+
return PAM_AUTH_ERR; // Fail if no authtok and no query_missing_token argument
49+
}
50+
}
51+
52+
// Ensure the authtok is not unreasonably long
53+
if (strlen(authtok) > PAM_MAX_RESP_SIZE) {
54+
pam_syslog(pamh, LOG_ERR, "PAM_AUTHTOK is too long.");
55+
return PAM_AUTH_ERR;
56+
}
57+
58+
// Find the position of the last '+' character
59+
char *plus_pos = strrchr(authtok, '+');
60+
if (plus_pos == NULL) {
61+
pam_syslog(pamh, LOG_ERR, "No '+' found in PAM_AUTHTOK.");
62+
return PAM_AUTH_ERR;
63+
}
64+
65+
// Split the authtok into password and token
66+
size_t password_len = plus_pos - authtok;
67+
char *password = strndup(authtok, password_len); // Copy only the password part
68+
if (password == NULL) {
69+
pam_syslog(pamh, LOG_ERR, "Failed to allocate memory for password.");
70+
return PAM_BUF_ERR;
71+
}
72+
73+
// Ensure the token is not empty
74+
if (*(plus_pos + 1) == '\0') {
75+
pam_syslog(pamh, LOG_ERR, "No token found after the last '+'.");
76+
free(password);
77+
return PAM_AUTH_ERR;
78+
}
79+
80+
const char *token = plus_pos + 1; // Token part starts after '+'
81+
82+
// Set the modified password (without the token) back into PAM_AUTHTOK
83+
pam_ret = pam_set_item(pamh, PAM_AUTHTOK, password);
84+
if (pam_ret != PAM_SUCCESS) {
85+
pam_syslog(pamh, LOG_ERR, "Failed to set PAM_AUTHTOK.");
86+
free(password);
87+
return PAM_AUTH_ERR;
88+
}
89+
90+
// Allocate memory for the environment variable to hold the token
91+
size_t token_env_size = strlen("PAM_SPLIT_TOKEN=") + strlen(token) + 1; // +1 for null terminator
92+
char *token_env = malloc(token_env_size);
93+
if (!token_env) {
94+
pam_syslog(pamh, LOG_ERR, "Failed to allocate memory for token environment variable.");
95+
free(password);
96+
return PAM_BUF_ERR;
97+
}
98+
99+
snprintf(token_env, token_env_size, "PAM_SPLIT_TOKEN=%s", token);
100+
101+
// Set PAM_SPLIT_TOKEN environment variable to the token
102+
pam_ret = pam_putenv(pamh, token_env);
103+
if (pam_ret != PAM_SUCCESS) {
104+
pam_syslog(pamh, LOG_ERR, "Failed to set PAM_SPLIT_TOKEN environment variable.");
105+
free(password);
106+
free(token_env);
107+
return PAM_AUTH_ERR;
108+
}
109+
110+
// Log success
111+
pam_syslog(pamh, LOG_INFO, "PAM_AUTHTOK and PAM_SPLIT_TOKEN set successfully.");
112+
113+
// Clean up
114+
free(password);
115+
free(token_env);
116+
117+
return PAM_SUCCESS;
118+
}
119+
120+
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
121+
return PAM_SUCCESS;
122+
}

0 commit comments

Comments
 (0)