pam_ptimpl.c 13 KB

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