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