pam_ptimpl.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. /** BEGIN COPYRIGHT BLOCK
  2. * This Program is free software; you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation; version 2 of the License.
  5. *
  6. * This Program is distributed in the hope that it will be useful, but WITHOUT
  7. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  9. *
  10. * You should have received a copy of the GNU General Public License along with
  11. * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
  12. * Place, Suite 330, Boston, MA 02111-1307 USA.
  13. *
  14. * In addition, as a special exception, Red Hat, Inc. gives You the additional
  15. * right to link the code of this Program with code not covered under the GNU
  16. * General Public License ("Non-GPL Code") and to distribute linked combinations
  17. * including the two, subject to the limitations in this paragraph. Non-GPL Code
  18. * permitted under this exception must only link to the code of this Program
  19. * through those well defined interfaces identified in the file named EXCEPTION
  20. * found in the source code files (the "Approved Interfaces"). The files of
  21. * Non-GPL Code may instantiate templates or use macros or inline functions from
  22. * the Approved Interfaces without causing the resulting work to be covered by
  23. * the GNU General Public License. Only Red Hat, Inc. may make changes or
  24. * additions to the list of Approved Interfaces. You must obey the GNU General
  25. * Public License in all respects for all of the Program code and other code used
  26. * in conjunction with the Program except the Non-GPL Code covered by this
  27. * exception. If you modify this file, you may extend this exception to your
  28. * version of the file, but you are not obligated to do so. If you do not wish to
  29. * provide this exception without modification, you must delete this exception
  30. * statement from your version and license this file solely under the GPL without
  31. * exception.
  32. *
  33. *
  34. * Copyright (C) 2005 Red Hat, Inc.
  35. * All rights reserved.
  36. * END COPYRIGHT BLOCK **/
  37. #ifdef HAVE_CONFIG_H
  38. # include <config.h>
  39. #endif
  40. #include <security/pam_appl.h>
  41. #include "pam_passthru.h"
  42. /*
  43. * PAM is not thread safe. We have to execute any PAM API calls in
  44. * a critical section. This is the lock that protects that code.
  45. */
  46. static Slapi_Mutex *PAMLock;
  47. /* Utility struct to wrap strings to avoid mallocs if possible - use
  48. stack allocated string space */
  49. #define MY_STATIC_BUF_SIZE 256
  50. typedef struct my_str_buf {
  51. char fixbuf[MY_STATIC_BUF_SIZE];
  52. char *str;
  53. } MyStrBuf;
  54. static char *
  55. init_my_str_buf(MyStrBuf *buf, const char *s)
  56. {
  57. PR_ASSERT(buf);
  58. if (s && (strlen(s) < sizeof(buf->fixbuf))) {
  59. strcpy(buf->fixbuf, s);
  60. buf->str = buf->fixbuf;
  61. } else {
  62. buf->str = slapi_ch_strdup(s);
  63. buf->fixbuf[0] = 0;
  64. }
  65. return buf->str;
  66. }
  67. static void
  68. delete_my_str_buf(MyStrBuf *buf)
  69. {
  70. if (buf->str != buf->fixbuf) {
  71. slapi_ch_free_string(&(buf->str));
  72. }
  73. }
  74. /* for third arg to pam_start */
  75. struct my_pam_conv_str {
  76. Slapi_PBlock *pb;
  77. char *pam_identity;
  78. };
  79. /*
  80. * Get the PAM identity from the value of the leftmost RDN in the BIND DN.
  81. */
  82. static char *
  83. derive_from_bind_dn(Slapi_PBlock *pb, const char *binddn, MyStrBuf *pam_id)
  84. {
  85. Slapi_RDN *rdn;
  86. char *type = NULL;
  87. char *value = NULL;
  88. rdn = slapi_rdn_new_dn(binddn);
  89. slapi_rdn_get_first(rdn, &type, &value);
  90. init_my_str_buf(pam_id, value);
  91. slapi_rdn_free(&rdn);
  92. return pam_id->str;
  93. }
  94. static char *
  95. derive_from_bind_entry(Slapi_PBlock *pb, const char *binddn,
  96. MyStrBuf *pam_id, char *map_ident_attr, int *locked)
  97. {
  98. char buf[BUFSIZ];
  99. Slapi_Entry *entry = NULL;
  100. Slapi_DN *sdn = slapi_sdn_new_dn_byref(binddn);
  101. char *attrs[] = { NULL, NULL };
  102. attrs[0] = map_ident_attr;
  103. int rc = slapi_search_internal_get_entry(sdn, attrs, &entry,
  104. pam_passthruauth_get_plugin_identity());
  105. slapi_sdn_free(&sdn);
  106. if (rc != LDAP_SUCCESS) {
  107. slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
  108. "Could not find BIND dn %s (error %d - %s)\n",
  109. escape_string(binddn, buf), rc, ldap_err2string(rc));
  110. init_my_str_buf(pam_id, NULL);
  111. } else if (NULL == entry) {
  112. slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
  113. "Could not find entry for BIND dn %s\n",
  114. escape_string(binddn, buf));
  115. init_my_str_buf(pam_id, NULL);
  116. } else if (slapi_check_account_lock( pb, entry, 0, 0, 0 ) == 1) {
  117. slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
  118. "Account %s inactivated.\n",
  119. escape_string(binddn, buf));
  120. init_my_str_buf(pam_id, NULL);
  121. *locked = 1;
  122. } else {
  123. char *val = slapi_entry_attr_get_charptr(entry, map_ident_attr);
  124. init_my_str_buf(pam_id, val);
  125. slapi_ch_free_string(&val);
  126. }
  127. slapi_entry_free(entry);
  128. return pam_id->str;
  129. }
  130. static void
  131. report_pam_error(char *str, int rc, pam_handle_t *pam_handle)
  132. {
  133. if (rc != PAM_SUCCESS) {
  134. slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
  135. "Error from PAM %s (%d: %s)\n",
  136. str, rc, pam_strerror(pam_handle, rc));
  137. }
  138. }
  139. /* returns a berval value as a null terminated string */
  140. static char *strdupbv(struct berval *bv)
  141. {
  142. char *str = slapi_ch_malloc(bv->bv_len+1);
  143. memcpy(str, bv->bv_val, bv->bv_len);
  144. str[bv->bv_len] = 0;
  145. return str;
  146. }
  147. static void
  148. free_pam_response(int nresp, struct pam_response *resp)
  149. {
  150. int ii;
  151. for (ii = 0; ii < nresp; ++ii) {
  152. if (resp[ii].resp) {
  153. slapi_ch_free((void **)&(resp[ii].resp));
  154. }
  155. }
  156. slapi_ch_free((void **)&resp);
  157. }
  158. /*
  159. * This is the conversation function passed into pam_start(). This is what sets the password
  160. * that PAM uses to authenticate. This function is sort of stupid - it assumes all echo off
  161. * or binary prompts are for the password, and other prompts are for the username. Time will
  162. * tell if this is actually the case.
  163. */
  164. static int
  165. pam_conv_func(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *mydata)
  166. {
  167. int ii;
  168. struct berval *creds;
  169. struct my_pam_conv_str *my_data = (struct my_pam_conv_str *)mydata;
  170. struct pam_response *reply;
  171. int ret = PAM_SUCCESS;
  172. if (num_msg <= 0) {
  173. return PAM_CONV_ERR;
  174. }
  175. /* empty reply structure */
  176. reply = (struct pam_response *)slapi_ch_calloc(num_msg,
  177. sizeof(struct pam_response));
  178. slapi_pblock_get( my_data->pb, SLAPI_BIND_CREDENTIALS, &creds ); /* the password */
  179. for (ii = 0; ii < num_msg; ++ii) {
  180. slapi_log_error(SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
  181. "pam msg [%d] = %d %s\n", ii, msg[ii]->msg_style,
  182. msg[ii]->msg);
  183. /* hard to tell what prompt is for . . . */
  184. /* assume prompts for password are either BINARY or ECHO_OFF */
  185. if (msg[ii]->msg_style == PAM_PROMPT_ECHO_OFF) {
  186. reply[ii].resp = strdupbv(creds);
  187. #ifdef LINUX
  188. } else if (msg[ii]->msg_style == PAM_BINARY_PROMPT) {
  189. reply[ii].resp = strdupbv(creds);
  190. #endif
  191. } else if (msg[ii]->msg_style == PAM_PROMPT_ECHO_ON) { /* assume username */
  192. reply[ii].resp = slapi_ch_strdup(my_data->pam_identity);
  193. } else if (msg[ii]->msg_style == PAM_ERROR_MSG) {
  194. slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
  195. "pam msg [%d] error [%s]\n", ii, msg[ii]->msg);
  196. } else if (msg[ii]->msg_style == PAM_TEXT_INFO) {
  197. slapi_log_error(SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
  198. "pam msg [%d] text info [%s]\n", ii, msg[ii]->msg);
  199. } else {
  200. slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
  201. "Error: unknown pam message type (%d: %s)\n",
  202. msg[ii]->msg_style, msg[ii]->msg);
  203. ret = PAM_CONV_ERR;
  204. }
  205. }
  206. if (ret == PAM_CONV_ERR) {
  207. free_pam_response(num_msg, reply);
  208. reply = NULL;
  209. }
  210. *resp = reply;
  211. return ret;
  212. }
  213. /*
  214. * Do the actual work of authenticating with PAM. First, get the PAM identity
  215. * based on the method used to convert the BIND identity to the PAM identity.
  216. * Set up the structures that pam_start needs and call pam_start(). After
  217. * that, call pam_authenticate and pam_acct_mgmt. Check the various return
  218. * values from these functions and map them to their corresponding LDAP BIND
  219. * return values. Return the appropriate LDAP error code.
  220. * This function will also set the appropriate LDAP response controls in
  221. * the given pblock.
  222. * Since this function can be called multiple times, we only want to return
  223. * the errors and controls to the user if this is the final call, so the
  224. * final_method parameter tells us if this is the last one. Coupled with
  225. * the fallback argument, we can tell if we are able to send the response
  226. * back to the client.
  227. */
  228. static int
  229. do_one_pam_auth(
  230. Slapi_PBlock *pb,
  231. int method, /* get pam identity from ENTRY, RDN, or DN */
  232. PRBool final_method, /* which method is the last one to try */
  233. char *pam_service, /* name of service for pam_start() */
  234. char *map_ident_attr, /* for ENTRY method, name of attribute holding pam identity */
  235. PRBool fallback, /* if true, failure here should fallback to regular bind */
  236. int pw_response_requested /* do we need to send pwd policy resp control */
  237. )
  238. {
  239. MyStrBuf pam_id;
  240. const char *binddn = NULL;
  241. Slapi_DN *bindsdn = NULL;
  242. int rc;
  243. int retcode = LDAP_SUCCESS;
  244. pam_handle_t *pam_handle;
  245. struct my_pam_conv_str my_data;
  246. struct pam_conv my_pam_conv = {pam_conv_func, NULL};
  247. char buf[BUFSIZ]; /* for error messages */
  248. char *errmsg = NULL; /* free with PR_smprintf_free */
  249. int locked = 0;
  250. slapi_pblock_get( pb, SLAPI_BIND_TARGET_SDN, &bindsdn );
  251. if (NULL == bindsdn) {
  252. errmsg = PR_smprintf("Null bind dn");
  253. retcode = LDAP_OPERATIONS_ERROR;
  254. pam_id.str = NULL; /* initialize pam_id.str */
  255. goto done; /* skip the pam stuff */
  256. }
  257. binddn = slapi_sdn_get_dn(bindsdn);
  258. if (method == PAMPT_MAP_METHOD_RDN) {
  259. derive_from_bind_dn(pb, binddn, &pam_id);
  260. } else if (method == PAMPT_MAP_METHOD_ENTRY) {
  261. derive_from_bind_entry(pb, binddn, &pam_id, map_ident_attr, &locked);
  262. } else {
  263. init_my_str_buf(&pam_id, binddn);
  264. }
  265. if (locked) {
  266. errmsg = PR_smprintf("Account inactivated. Contact system administrator.");
  267. retcode = LDAP_UNWILLING_TO_PERFORM; /* user inactivated */
  268. goto done; /* skip the pam stuff */
  269. }
  270. if (!pam_id.str) {
  271. errmsg = PR_smprintf("Bind DN [%s] is invalid or not found",
  272. escape_string(binddn, buf));
  273. retcode = LDAP_NO_SUCH_OBJECT; /* user unknown */
  274. goto done; /* skip the pam stuff */
  275. }
  276. /* do the pam stuff */
  277. my_data.pb = pb;
  278. my_data.pam_identity = pam_id.str;
  279. my_pam_conv.appdata_ptr = &my_data;
  280. slapi_lock_mutex(PAMLock);
  281. /* from this point on we are in the critical section */
  282. rc = pam_start(pam_service, pam_id.str, &my_pam_conv, &pam_handle);
  283. report_pam_error("during pam_start", rc, pam_handle);
  284. if (rc == PAM_SUCCESS) {
  285. /* use PAM_SILENT - there is no user interaction at this point */
  286. rc = pam_authenticate(pam_handle, 0);
  287. report_pam_error("during pam_authenticate", rc, pam_handle);
  288. /* check different types of errors here */
  289. if (rc == PAM_USER_UNKNOWN) {
  290. errmsg = PR_smprintf("User id [%s] for bind DN [%s] does not exist in PAM",
  291. pam_id.str, escape_string(binddn, buf));
  292. retcode = LDAP_NO_SUCH_OBJECT; /* user unknown */
  293. } else if (rc == PAM_AUTH_ERR) {
  294. errmsg = PR_smprintf("Invalid PAM password for user id [%s], bind DN [%s]",
  295. pam_id.str, escape_string(binddn, buf));
  296. retcode = LDAP_INVALID_CREDENTIALS; /* invalid creds */
  297. } else if (rc == PAM_MAXTRIES) {
  298. errmsg = PR_smprintf("Authentication retry limit exceeded in PAM for "
  299. "user id [%s], bind DN [%s]",
  300. pam_id.str, escape_string(binddn, buf));
  301. if (pw_response_requested) {
  302. slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_ACCTLOCKED);
  303. }
  304. retcode = LDAP_CONSTRAINT_VIOLATION; /* max retries */
  305. } else if (rc != PAM_SUCCESS) {
  306. errmsg = PR_smprintf("Unknown PAM error [%s] for user id [%s], bind DN [%s]",
  307. pam_strerror(pam_handle, rc), pam_id.str, escape_string(binddn, buf));
  308. retcode = LDAP_OPERATIONS_ERROR; /* pam config or network problem */
  309. }
  310. }
  311. /* if user authenticated successfully, see if there is anything we need
  312. to report back w.r.t. password or account lockout */
  313. if (rc == PAM_SUCCESS) {
  314. rc = pam_acct_mgmt(pam_handle, 0);
  315. report_pam_error("during pam_acct_mgmt", rc, pam_handle);
  316. /* check different types of errors here */
  317. if (rc == PAM_USER_UNKNOWN) {
  318. errmsg = PR_smprintf("User id [%s] for bind DN [%s] does not exist in PAM",
  319. pam_id.str, escape_string(binddn, buf));
  320. retcode = LDAP_NO_SUCH_OBJECT; /* user unknown */
  321. } else if (rc == PAM_AUTH_ERR) {
  322. errmsg = PR_smprintf("Invalid PAM password for user id [%s], bind DN [%s]",
  323. pam_id.str, escape_string(binddn, buf));
  324. retcode = LDAP_INVALID_CREDENTIALS; /* invalid creds */
  325. } else if (rc == PAM_PERM_DENIED) {
  326. errmsg = PR_smprintf("Access denied for PAM user id [%s], bind DN [%s]"
  327. " - see administrator",
  328. pam_id.str, escape_string(binddn, buf));
  329. if (pw_response_requested) {
  330. slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_ACCTLOCKED);
  331. }
  332. retcode = LDAP_UNWILLING_TO_PERFORM;
  333. } else if (rc == PAM_ACCT_EXPIRED) {
  334. errmsg = PR_smprintf("Expired PAM password for user id [%s], bind DN [%s]: "
  335. "reset required",
  336. pam_id.str, escape_string(binddn, buf));
  337. slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
  338. if (pw_response_requested) {
  339. slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_PWDEXPIRED);
  340. }
  341. retcode = LDAP_INVALID_CREDENTIALS;
  342. } else if (rc == PAM_NEW_AUTHTOK_REQD) { /* handled same way as ACCT_EXPIRED */
  343. errmsg = PR_smprintf("Expired PAM password for user id [%s], bind DN [%s]: "
  344. "reset required",
  345. pam_id.str, escape_string(binddn, buf));
  346. slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
  347. if (pw_response_requested) {
  348. slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_PWDEXPIRED);
  349. }
  350. retcode = LDAP_INVALID_CREDENTIALS;
  351. } else if (rc != PAM_SUCCESS) {
  352. errmsg = PR_smprintf("Unknown PAM error [%s] for user id [%s], bind DN [%s]",
  353. pam_strerror(pam_handle, rc), pam_id.str, escape_string(binddn, buf));
  354. retcode = LDAP_OPERATIONS_ERROR; /* unknown */
  355. }
  356. }
  357. rc = pam_end(pam_handle, rc);
  358. report_pam_error("during pam_end", rc, pam_handle);
  359. slapi_unlock_mutex(PAMLock);
  360. /* not in critical section any more */
  361. done:
  362. delete_my_str_buf(&pam_id);
  363. if ((retcode == LDAP_SUCCESS) && (rc != PAM_SUCCESS)) {
  364. errmsg = PR_smprintf("Unknown PAM error [%d] for user id [%s], bind DN [%s]",
  365. rc, pam_id.str, escape_string(binddn, buf));
  366. retcode = LDAP_OPERATIONS_ERROR;
  367. }
  368. if (retcode != LDAP_SUCCESS) {
  369. slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
  370. "%s\n", errmsg);
  371. if (final_method && !fallback) {
  372. slapi_send_ldap_result(pb, retcode, NULL, errmsg, 0, NULL);
  373. }
  374. }
  375. if (errmsg) {
  376. PR_smprintf_free(errmsg);
  377. }
  378. return retcode;
  379. }
  380. /*
  381. * Perform any PAM subsystem initialization that must be done at startup time.
  382. * For now, this means only the PAM mutex since PAM is not thread safe.
  383. */
  384. int
  385. pam_passthru_pam_init( void )
  386. {
  387. if (!(PAMLock = slapi_new_mutex())) {
  388. return LDAP_LOCAL_ERROR;
  389. }
  390. return 0;
  391. }
  392. /*
  393. * Entry point into the PAM auth code. Shields the rest of the app
  394. * from PAM API code. Get our config params, then call the actual
  395. * code that does the PAM auth. Can call that code up to 3 times,
  396. * depending on what methods are set in the config.
  397. */
  398. int
  399. pam_passthru_do_pam_auth(Slapi_PBlock *pb, Pam_PassthruConfig *cfg)
  400. {
  401. int rc = LDAP_SUCCESS;
  402. MyStrBuf pam_id_attr; /* avoid malloc if possible */
  403. MyStrBuf pam_service; /* avoid malloc if possible */
  404. int method1, method2, method3;
  405. PRBool final_method;
  406. PRBool fallback = PR_FALSE;
  407. int pw_response_requested;
  408. LDAPControl **reqctrls = NULL;
  409. /* first lock and get the methods and other info */
  410. /* we do this so we can acquire and release the lock quickly to
  411. avoid potential deadlocks */
  412. slapi_lock_mutex(cfg->lock);
  413. method1 = cfg->pamptconfig_map_method1;
  414. method2 = cfg->pamptconfig_map_method2;
  415. method3 = cfg->pamptconfig_map_method3;
  416. init_my_str_buf(&pam_id_attr, cfg->pamptconfig_pam_ident_attr);
  417. init_my_str_buf(&pam_service, cfg->pamptconfig_service);
  418. fallback = cfg->pamptconfig_fallback;
  419. slapi_unlock_mutex(cfg->lock);
  420. slapi_pblock_get (pb, SLAPI_REQCONTROLS, &reqctrls);
  421. slapi_pblock_get (pb, SLAPI_PWPOLICY, &pw_response_requested);
  422. /* figure out which method is the last one - we only return error codes, controls
  423. to the client and send a response on the last method */
  424. final_method = (method2 == PAMPT_MAP_METHOD_NONE);
  425. rc = do_one_pam_auth(pb, method1, final_method, pam_service.str, pam_id_attr.str, fallback,
  426. pw_response_requested);
  427. if ((rc != LDAP_SUCCESS) && !final_method) {
  428. final_method = (method3 == PAMPT_MAP_METHOD_NONE);
  429. rc = do_one_pam_auth(pb, method2, final_method, pam_service.str, pam_id_attr.str, fallback,
  430. pw_response_requested);
  431. if ((rc != LDAP_SUCCESS) && !final_method) {
  432. final_method = PR_TRUE;
  433. rc = do_one_pam_auth(pb, method3, final_method, pam_service.str, pam_id_attr.str, fallback,
  434. pw_response_requested);
  435. }
  436. }
  437. delete_my_str_buf(&pam_id_attr);
  438. delete_my_str_buf(&pam_service);
  439. return rc;
  440. }