pam_ptimpl.c 15 KB

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