2 * Copyright (c) 2015 Nathan Holstein <nathan.holstein@gmail.com>
3 * Copyright (c) 2021-2022 Sergey Sushilin <sergeysushilin@protonmail.com>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <sys/types.h>
32 #include <security/pam_appl.h>
35 #include "readpassphrase.h"
39 # define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
42 #define PAM_SERVICE_NAME "doas"
44 static pam_handle_t
*pamh
= NULL
;
46 static char const *doas_prompt
;
48 static __nonnull((1, 3)) char *pamprompt(char const *restrict msg
, bool echo_on
, int *restrict ret
)
51 char *pass
, buf
[PAM_MAX_RESP_SIZE
];
52 int flags
= RPP_REQUIRE_TTY
| (echo_on
? RPP_ECHO_ON
: RPP_ECHO_OFF
);
54 /* Overwrite default prompt if it matches "Password:[ ]". */
55 if (strneq(msg
, "Password:", 9) && (msg
[9] == '\0' || (msg
[9] == ' ' && msg
[10] == '\0')))
60 pass
= readpassphrase(prompt
, buf
, sizeof(buf
), flags
);
69 explicit_bzero(buf
, sizeof(buf
));
73 static int pamconv(int nmsgs
, struct pam_message
const **restrict msgs
, struct pam_response
**restrict rsps
, void *restrict ptr __unused
)
76 struct pam_response
*rsp
= xcalloc(nmsgs
, sizeof(*rsp
));
78 for (i
= 0; i
< nmsgs
; i
++) {
79 switch (style
= msgs
[i
]->msg_style
) {
80 case PAM_PROMPT_ECHO_OFF
:
81 case PAM_PROMPT_ECHO_ON
: {
84 rsp
[i
].resp
= pamprompt(msgs
[i
]->msg
,
85 style
== PAM_PROMPT_ECHO_ON
,
88 if (ret
!= PAM_SUCCESS
)
95 int fd
= (style
!= PAM_ERROR_MSG
? STDOUT_FILENO
: STDERR_FILENO
);
96 size_t msglen
= strlen(msgs
[i
]->msg
);
98 if (full_write(fd
, msgs
[i
]->msg
, msglen
) != msglen
)
104 errx(EXIT_FAILURE
, "invalid PAM msg_style %d", style
);
113 /* Overwrite and free response buffers. */
114 for (i
= 0; i
< nmsgs
; i
++) {
115 if (rsp
[i
].resp
== NULL
)
118 switch (msgs
[i
]->msg_style
) {
119 case PAM_PROMPT_ECHO_OFF
:
120 case PAM_PROMPT_ECHO_ON
:
121 explicit_bzero(rsp
[i
].resp
, strlen(rsp
[i
].resp
));
132 static void pamcleanup(int ret
, bool sess
, bool cred
)
135 ret
= pam_close_session(pamh
, 0);
137 if (ret
!= PAM_SUCCESS
)
138 errx(EXIT_FAILURE
, "pam_close_session: %s", pam_strerror(pamh
, ret
));
142 ret
= pam_setcred(pamh
, PAM_DELETE_CRED
| PAM_SILENT
);
144 if (ret
!= PAM_SUCCESS
)
145 warn("pam_setcred(?, PAM_DELETE_CRED | PAM_SILENT): %s", pam_strerror(pamh
, ret
));
151 void pamauth(char const *restrict prompt
, char const *target_name
, char const *original_name
)
153 static struct pam_conv
const conv
= { .conv
= pamconv
, .appdata_ptr
= NULL
};
156 bool sess
= false, cred
= false;
158 doas_prompt
= prompt
;
160 if (target_name
== NULL
|| original_name
== NULL
)
161 errx(EXIT_FAILURE
, "Authentication failed");
163 ret
= pam_start(PAM_SERVICE_NAME
, original_name
, &conv
, &pamh
);
165 if (ret
!= PAM_SUCCESS
)
166 errx(EXIT_FAILURE
, "pam_start(\"%s\", \"%s\", ?, ?): failed", PAM_SERVICE_NAME
, original_name
);
168 ret
= pam_set_item(pamh
, PAM_RUSER
, original_name
);
170 if (ret
!= PAM_SUCCESS
)
171 warn("pam_set_item(?, PAM_RUSER, \"%s\"): %s", pam_strerror(pamh
, ret
), original_name
);
173 if (isatty(STDIN_FILENO
) && (ttydev
= ttyname(STDIN_FILENO
)) != NULL
) {
174 if (strneq(ttydev
, "/dev/", 5))
177 ret
= pam_set_item(pamh
, PAM_TTY
, ttydev
);
179 if (ret
!= PAM_SUCCESS
)
180 warn("pam_set_item(?, PAM_TTY, \"%s\"): %s", ttydev
, pam_strerror(pamh
, ret
));
184 ret
= pam_authenticate(pamh
, 0);
186 if (ret
!= PAM_SUCCESS
) {
187 pamcleanup(ret
, sess
, cred
);
188 syslog(LOG_AUTHPRIV
| LOG_NOTICE
, "failed auth for %s", original_name
);
189 errx(EXIT_FAILURE
, "Authentication failed");
192 ret
= pam_acct_mgmt(pamh
, 0);
194 if (ret
== PAM_NEW_AUTHTOK_REQD
)
195 ret
= pam_chauthtok(pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
197 /* Account not vaild or changing the auth token failed. */
198 if (ret
!= PAM_SUCCESS
) {
199 pamcleanup(ret
, sess
, cred
);
200 syslog(LOG_AUTHPRIV
| LOG_NOTICE
, "failed auth for %s", original_name
);
201 errx(EXIT_FAILURE
, "Authentication failed");
204 /* Set PAM_USER to the user we want to be. */
205 ret
= pam_set_item(pamh
, PAM_USER
, target_name
);
207 if (ret
!= PAM_SUCCESS
)
208 warn("pam_set_item(?, PAM_USER, \"%s\"): %s", target_name
, pam_strerror(pamh
, ret
));
210 ret
= pam_setcred(pamh
, PAM_REINITIALIZE_CRED
);
212 if (ret
!= PAM_SUCCESS
)
213 warn("pam_setcred(?, PAM_REINITIALIZE_CRED): %s", pam_strerror(pamh
, ret
));
218 ret
= pam_open_session(pamh
, 0);
220 if (ret
!= PAM_SUCCESS
)
221 errx(EXIT_FAILURE
, "pam_open_session: %s", pam_strerror(pamh
, ret
));
224 pamcleanup(PAM_SUCCESS
, sess
, cred
);