1 /*
2 * pam_duo.c
3 *
4 * Copyright (c) 2010 Duo Security
5 * All rights reserved, all wrongs reversed.
6 */
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif
11
12 #include <sys/param.h>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
16
17 #include <ctype.h>
18 #include <errno.h>
19 #include <grp.h>
20 #include <limits.h>
21 #include <netdb.h>
22 #include <pwd.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <syslog.h>
26 #include <unistd.h>
27 #include <openssl/crypto.h>
28 #include <openssl/err.h>
29
30 /* These #defines must be present according to PAM documentation. */
31 #define PAM_SM_AUTH
32 #define PAM_SM_ACCOUNT
33 #define PAM_SM_SESSION
34 #define PAM_SM_PASSWORD
35
36 /* NetBSD PAM b0rkage (gnat 39313) */
37 #ifdef __NetBSD__
38 #define NO_STATIC_MODULES
39 #endif
40
41 #ifdef HAVE_SECURITY_PAM_APPL_H
42 #include <security/pam_appl.h>
43 #endif
44 #ifdef HAVE_SECURITY_PAM_MODULES_H
45 #include <security/pam_modules.h>
46 #endif
47 #ifdef HAVE_SECURITY_PAM_EXT_H
48 #include <security/pam_ext.h> /* Linux-PAM */
49 #endif
50
51 /* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */
52 #if defined(__LINUX_PAM__) || defined(OPENPAM)
53 # define duopam_const const /* LinuxPAM, OpenPAM */
54 #else
55 # define duopam_const /* Solaris, HP-UX, AIX */
56 #endif
57
58 #include "util.h"
59 #include "duo.h"
60 #include "groupaccess.h"
61 #include "pam_extra.h"
62 #include "pam_duo_private.h"
63
64 #ifndef PAM_EXTERN
65 #define PAM_EXTERN
66 #endif
67
68 #ifndef DUO_PRIVSEP_USER
69 # define DUO_PRIVSEP_USER "duo"
70 #endif
71 #define DUO_CONF DUO_CONF_DIR "/pam_duo.conf"
72
73 static int
__ini_handler(void * u,const char * section,const char * name,const char * val)74 __ini_handler(void *u, const char *section, const char *name, const char *val)
75 {
76 struct duo_config *cfg = (struct duo_config *)u;
77 if (!duo_common_ini_handler(cfg, section, name, val)) {
78 /* There are no options specific to pam_duo yet */
79 duo_syslog(LOG_ERR, "Invalid pam_duo option: '%s'", name);
80 return (0);
81 }
82 return (1);
83 }
84
85 static void
__duo_status(void * arg,const char * msg)86 __duo_status(void *arg, const char *msg)
87 {
88 pam_info((pam_handle_t *)arg, "%s", msg);
89 }
90
91 static char *
__duo_prompt(void * arg,const char * prompt,char * buf,size_t bufsz)92 __duo_prompt(void *arg, const char *prompt, char *buf, size_t bufsz)
93 {
94 char *p = NULL;
95
96 if (pam_prompt((pam_handle_t *)arg, PAM_PROMPT_ECHO_ON, &p,
97 "%s", prompt) != PAM_SUCCESS || p == NULL) {
98 return (NULL);
99 }
100 strlcpy(buf, p, bufsz);
101 free(p);
102 return (buf);
103 }
104
105 PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int pam_flags,int argc,const char * argv[])106 pam_sm_authenticate(pam_handle_t *pamh, int pam_flags,
107 int argc, const char *argv[])
108 {
109 struct duo_config cfg;
110 struct passwd *pw;
111 struct in_addr addr;
112 duo_t *duo;
113 duo_code_t code;
114
115 /*
116 * Only variables that will be passed to a pam_* function
117 * need to be marked as 'duopam_const char *', anything else
118 * should be 'const char *'. This is because there are different
119 * PAM implementations, some with the const qualifier, and some
120 * without.
121 */
122 duopam_const char *ip, *service, *user;
123 const char *cmd, *p, *config, *host;
124
125 int i, flags, pam_err, matched;
126
127 duo_config_default(&cfg);
128
129 /* Parse configuration */
130 config = DUO_CONF;
131 if(parse_argv(&config, argc, argv) == 0) {
132 return (PAM_SERVICE_ERR);
133 }
134
135 i = duo_parse_config(config, __ini_handler, &cfg);
136 if (i == -2) {
137 duo_syslog(LOG_ERR, "%s must be readable only by user 'root'",
138 config);
139 return (cfg.failmode == DUO_FAIL_SAFE ? PAM_SUCCESS : PAM_SERVICE_ERR);
140 } else if (i == -1) {
141 duo_syslog(LOG_ERR, "Couldn't open %s: %s",
142 config, strerror(errno));
143 return (cfg.failmode == DUO_FAIL_SAFE ? PAM_SUCCESS : PAM_SERVICE_ERR);
144 } else if (i > 0) {
145 duo_syslog(LOG_ERR, "Parse error in %s, line %d", config, i);
146 return (cfg.failmode == DUO_FAIL_SAFE ? PAM_SUCCESS : PAM_SERVICE_ERR);
147 } else if (!cfg.apihost || !cfg.apihost[0] ||
148 !cfg.skey || !cfg.skey[0] || !cfg.ikey || !cfg.ikey[0]) {
149 duo_syslog(LOG_ERR, "Missing host, ikey, or skey in %s", config);
150 return (cfg.failmode == DUO_FAIL_SAFE ? PAM_SUCCESS : PAM_SERVICE_ERR);
151 }
152
153 #ifdef OPENSSL_FIPS
154 /*
155 * When fips_mode is configured, invoke OpenSSL's FIPS_mode_set() API. Note
156 * that in some environments, FIPS may be enabled system-wide, causing FIPS
157 * operation to be enabled automatically when OpenSSL is initialized. The
158 * fips_mode option is an experimental feature allowing explicit entry to FIPS
159 * operation in cases where it isn't enabled globally at the OS level (for
160 * example, when integrating directly with the OpenSSL FIPS Object Module).
161 */
162 if(!FIPS_mode_set(cfg.fips_mode)) {
163 /* The smallest size buff can be according to the openssl docs */
164 char buff[256];
165 int error = ERR_get_error();
166 ERR_error_string_n(error, buff, sizeof(buff));
167 duo_syslog(LOG_ERR, "Unable to start fips_mode: %s", buff);
168
169 return (EXIT_FAILURE);
170 }
171 #else
172 if(cfg.fips_mode) {
173 duo_syslog(LOG_ERR, "FIPS mode flag specified, but OpenSSL not built with FIPS support. Failing the auth.");
174 return (EXIT_FAILURE);
175 }
176 #endif
177 /* Check user */
178 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS ||
179 (pw = getpwnam(user)) == NULL) {
180 close_config(&cfg);
181 return (PAM_USER_UNKNOWN);
182 }
183 /* XXX - Service-specific behavior */
184 flags = 0;
185 cmd = NULL;
186 if (pam_get_item(pamh, PAM_SERVICE, (duopam_const void **)
187 (duopam_const void *)&service) != PAM_SUCCESS) {
188 close_config(&cfg);
189 return (PAM_SERVICE_ERR);
190 }
191 if (strcmp(service, "sshd") == 0) {
192 /*
193 * Disable incremental status reporting for sshd :-(
194 * OpenSSH accumulates PAM_TEXT_INFO from modules to send in
195 * an SSH_MSG_USERAUTH_BANNER post-auth, not real-time!
196 */
197 flags |= DUO_FLAG_SYNC;
198 } else if (strcmp(service, "sudo") == 0) {
199 cmd = getenv("SUDO_COMMAND");
200 } else if (strcmp(service, "su") == 0 || strcmp(service, "su-l") == 0) {
201 /* Check calling user for Duo auth, just like sudo */
202 if ((pw = getpwuid(getuid())) == NULL) {
203 close_config(&cfg);
204 return (PAM_USER_UNKNOWN);
205 }
206 user = pw->pw_name;
207 }
208 /* Check group membership */
209 matched = duo_check_groups(pw, cfg.groups, cfg.groups_cnt);
210 if (matched == -1) {
211 close_config(&cfg);
212 return (PAM_SERVICE_ERR);
213 } else if (matched == 0) {
214 duo_syslog(LOG_INFO, "User %s bypassed Duo 2FA due to user's UNIX group", user);
215 close_config(&cfg);
216 return (PAM_SUCCESS);
217 }
218
219 /* Use GECOS field if called for */
220 if (cfg.send_gecos || cfg.gecos_username_pos >= 0) {
221 if (strlen(pw->pw_gecos) > 0) {
222 if (cfg.gecos_username_pos >= 0) {
223 user = duo_split_at(pw->pw_gecos, cfg.gecos_delim, cfg.gecos_username_pos);
224 if (user == NULL || (strcmp(user, "") == 0)) {
225 duo_log(LOG_DEBUG, "Could not parse GECOS field", pw->pw_name, NULL, NULL);
226 user = pw->pw_name;
227 }
228 } else {
229 user = pw->pw_gecos;
230 }
231 } else {
232 duo_log(LOG_WARNING, "Empty GECOS field", pw->pw_name, NULL, NULL);
233 }
234 }
235
236 /* Grab the remote host */
237 ip = NULL;
238 pam_get_item(pamh, PAM_RHOST,
239 (duopam_const void **)(duopam_const void *)&ip);
240 host = ip;
241 /* PAM is weird, check to see if PAM_RHOST is IP or hostname */
242 if (ip == NULL) {
243 ip = ""; /* XXX inet_addr needs a non-null IP */
244 }
245 if (!inet_aton(ip, &addr)) {
246 /* We have a hostname, don't try to resolve, check fallback */
247 if (cfg.local_ip_fallback) {
248 host = duo_local_ip();
249 }
250 }
251
252 /* Try Duo auth */
253 if ((duo = duo_open(cfg.apihost, cfg.ikey, cfg.skey,
254 "pam_duo/" PACKAGE_VERSION,
255 cfg.noverify ? "" : cfg.cafile, cfg.https_timeout, cfg.http_proxy)) == NULL) {
256 duo_log(LOG_ERR, "Couldn't open Duo API handle", pw->pw_name, host, NULL);
257 close_config(&cfg);
258 return (PAM_SERVICE_ERR);
259 }
260 duo_set_conv_funcs(duo, __duo_prompt, __duo_status, pamh);
261
262 if (cfg.autopush) {
263 flags |= DUO_FLAG_AUTO;
264 }
265
266 pam_err = PAM_SERVICE_ERR;
267
268 for (i = 0; i < cfg.prompts; i++) {
269 code = duo_login(duo, user, host, flags,
270 cfg.pushinfo ? cmd : NULL, cfg.failmode);
271 if (code == DUO_FAIL) {
272 duo_log(LOG_WARNING, "Failed Duo login",
273 pw->pw_name, host, duo_geterr(duo));
274 if ((flags & DUO_FLAG_SYNC) == 0) {
275 pam_info(pamh, "%s", "");
276 }
277 /* Keep going */
278 continue;
279 }
280 /* Terminal conditions */
281 if (code == DUO_OK) {
282 if ((p = duo_geterr(duo)) != NULL) {
283 duo_log(LOG_WARNING, "Skipped Duo login",
284 user, host, p);
285 } else {
286 duo_log(LOG_INFO, "Successful Duo login",
287 user, host, NULL);
288 }
289 pam_err = PAM_SUCCESS;
290 } else if (code == DUO_ABORT) {
291 duo_log(LOG_WARNING, "Aborted Duo login",
292 user, host, duo_geterr(duo));
293 pam_err = PAM_ABORT;
294 } else if (code == DUO_FAIL_SAFE_ALLOW) {
295 duo_log(LOG_WARNING, "Failsafe Duo login",
296 user, host, duo_geterr(duo));
297 pam_err = PAM_SUCCESS;
298 } else if (code == DUO_FAIL_SECURE_DENY) {
299 duo_log(LOG_WARNING, "Failsecure Duo login",
300 user, host, duo_geterr(duo));
301 pam_err = PAM_SERVICE_ERR;
302 } else {
303 duo_log(LOG_ERR, "Error in Duo login",
304 user, host, duo_geterr(duo));
305 pam_err = PAM_SERVICE_ERR;
306 }
307 break;
308 }
309 if (i == MAX_PROMPTS) {
310 pam_err = PAM_MAXTRIES;
311 }
312 duo_close(duo);
313 close_config(&cfg);
314
315 return (pam_err);
316 }
317
318 PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char * argv[])319 pam_sm_setcred(pam_handle_t *pamh, int flags,
320 int argc, const char *argv[])
321 {
322 return (PAM_SUCCESS);
323 }
324
325 PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags,int argc,const char * argv[])326 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
327 int argc, const char *argv[])
328 {
329 return (PAM_SUCCESS);
330 }
331
332 PAM_EXTERN int
pam_sm_open_session(pam_handle_t * pamh,int flags,int argc,const char * argv[])333 pam_sm_open_session(pam_handle_t *pamh, int flags,
334 int argc, const char *argv[])
335 {
336 return (PAM_SUCCESS);
337 }
338
339 PAM_EXTERN int
pam_sm_close_session(pam_handle_t * pamh,int flags,int argc,const char * argv[])340 pam_sm_close_session(pam_handle_t *pamh, int flags,
341 int argc, const char *argv[])
342 {
343 return (PAM_SUCCESS);
344 }
345
346 PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char * argv[])347 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
348 int argc, const char *argv[])
349 {
350 return (PAM_SERVICE_ERR);
351 }
352
353 #ifdef PAM_MODULE_ENTRY
354 PAM_MODULE_ENTRY("pam_duo");
355 #endif
356