pam_secret.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. /*
  2. * $Id$
  3. *
  4. * Copyright (c) 1999 Andrew G. Morgan <morgan@linux.kernel.org>
  5. */
  6. /*
  7. * WARNING: AS WRITTEN THIS CODE IS NOT SECURE. THE MD5 IMPLEMENTATION
  8. * NEEDS TO BE INTEGRATED MORE NATIVELY.
  9. */
  10. #include <fcntl.h>
  11. #include <pwd.h>
  12. #include <stdio.h>
  13. #include <string.h>
  14. #include <sys/types.h>
  15. #include <sys/stat.h>
  16. #include <security/pam_modules.h>
  17. #include <security/pam_client.h>
  18. #include <security/_pam_macros.h>
  19. /*
  20. * This is a sample module that demonstrates the use of binary prompts
  21. * and how they can be used to implement sophisticated authentication
  22. * schemes.
  23. */
  24. struct ps_state_s {
  25. int retval; /* last retval returned by the authentication fn */
  26. int state; /* what state the module was in when it
  27. returned incomplete */
  28. char *username; /* the name of the local user */
  29. char server_cookie[33]; /* storage for 32 bytes of server cookie */
  30. char client_cookie[33]; /* storage for 32 bytes of client cookie */
  31. char *secret_data; /* pointer to <NUL> terminated secret_data */
  32. int invalid_secret; /* indication of whether the secret is valid */
  33. pamc_bp_t current_prompt; /* place to store the current prompt */
  34. pamc_bp_t current_reply; /* place to receive the reply prompt */
  35. };
  36. #define PS_STATE_ID "PAM_SECRET__STATE"
  37. #define PS_AGENT_ID "secret@here"
  38. #define PS_STATE_DEAD 0
  39. #define PS_STATE_INIT 1
  40. #define PS_STATE_PROMPT1 2
  41. #define PS_STATE_PROMPT2 3
  42. #define MAX_LEN_HOSTNAME 512
  43. #define MAX_FILE_LINE_LEN 1024
  44. /*
  45. * Routine for generating 16*8 bits of random data represented in ASCII hex
  46. */
  47. static int generate_cookie(unsigned char *buffer_33)
  48. {
  49. static const char hexarray[] = "0123456789abcdef";
  50. int i, fd;
  51. /* fill buffer_33 with 32 hex characters (lower case) + '\0' */
  52. fd = open("/dev/urandom", O_RDONLY);
  53. if (fd < 0) {
  54. D(("failed to open /dev/urandom"));
  55. return 0;
  56. }
  57. read(fd, buffer_33 + 16, 16);
  58. close(fd);
  59. /* expand top 16 bytes into 32 nibbles */
  60. for (i=0; i<16; ++i) {
  61. buffer_33[2*i ] = hexarray[(buffer_33[16+i] & 0xf0)>>4];
  62. buffer_33[2*i+1] = hexarray[(buffer_33[16+i] & 0x0f)];
  63. }
  64. buffer_33[32] = '\0';
  65. return 1;
  66. }
  67. /*
  68. * XXX - This is a hack, and is fundamentally insecure. Its subject to
  69. * all sorts of attacks not to mention the fact that all our secrets
  70. * will be displayed on the command line for someone doing 'ps' to
  71. * see. This is just for programming convenience in this instance, it
  72. * needs to be replaced with the md5 code. Although I am loath to
  73. * add yet another instance of md5 code to the Linux-PAM source code.
  74. * [Need to think of a cleaner way to do this for the distribution as
  75. * a whole...]
  76. */
  77. #define COMMAND_FORMAT "/bin/echo -n '%s|%s|%s'|/usr/bin/md5sum -"
  78. int create_digest(const char *d1, const char *d2, const char *d3,
  79. char *buffer_33)
  80. {
  81. int length;
  82. char *buffer;
  83. FILE *pipe;
  84. length = strlen(d1)+strlen(d2)+strlen(d3)+sizeof(COMMAND_FORMAT);
  85. buffer = malloc(length);
  86. if (buffer == NULL) {
  87. D(("out of memory"));
  88. return 0;
  89. }
  90. sprintf(buffer, COMMAND_FORMAT, d1,d2,d3);
  91. D(("executing pipe [%s]", buffer));
  92. pipe = popen(buffer, "r");
  93. memset(buffer, 0, length);
  94. free(buffer);
  95. if (pipe == NULL) {
  96. D(("failed to launch pipe"));
  97. return 0;
  98. }
  99. if (fgets(buffer_33, 33, pipe) == NULL) {
  100. D(("failed to read digest"));
  101. return 0;
  102. }
  103. if (strlen(buffer_33) != 32) {
  104. D(("digest was not 32 chars"));
  105. return 0;
  106. }
  107. fclose(pipe);
  108. D(("done [%s]", buffer_33));
  109. return 1;
  110. }
  111. /*
  112. * method to attempt to instruct the application's conversation function
  113. */
  114. static int converse(pam_handle_t *pamh, struct ps_state_s *new)
  115. {
  116. int retval;
  117. struct pam_conv *conv;
  118. D(("called"));
  119. retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
  120. if (retval == PAM_SUCCESS) {
  121. struct pam_message msg;
  122. struct pam_response *single_reply;
  123. const struct pam_message *msg_ptr;
  124. memset(&msg, 0, sizeof(msg));
  125. msg.msg_style = PAM_BINARY_PROMPT;
  126. msg.msg = (const char *) new->current_prompt;
  127. msg_ptr = &msg;
  128. single_reply = NULL;
  129. retval = conv->conv(1, &msg_ptr, &single_reply, conv->appdata_ptr);
  130. if (retval == PAM_SUCCESS) {
  131. if ((single_reply == NULL) || (single_reply->resp == NULL)) {
  132. retval == PAM_CONV_ERR;
  133. } else {
  134. new->current_reply = (pamc_bp_t) single_reply->resp;
  135. single_reply->resp = NULL;
  136. }
  137. }
  138. if (single_reply) {
  139. free(single_reply);
  140. }
  141. }
  142. #ifdef PAM_DEBUG
  143. if (retval == PAM_SUCCESS) {
  144. D(("reply has length=%d and control=%u",
  145. PAM_BP_LENGTH(new->current_reply),
  146. PAM_BP_CONTROL(new->current_reply)));
  147. }
  148. D(("returning %s", pam_strerror(pamh, retval)));
  149. #endif
  150. return retval;
  151. }
  152. /*
  153. * identify the secret in question
  154. */
  155. #define SECRET_FILE_FORMAT "%s/.secret@here"
  156. char *identify_secret(char *identity, const char *user)
  157. {
  158. struct passwd *pwd;
  159. char *temp;
  160. FILE *secrets;
  161. int length_id;
  162. pwd = getpwnam(user);
  163. if ((pwd == NULL) || (pwd->pw_dir == NULL)) {
  164. D(("user [%s] is not known", user));
  165. return NULL;
  166. }
  167. length_id = strlen(pwd->pw_dir) + sizeof(SECRET_FILE_FORMAT);
  168. temp = malloc(length_id);
  169. if (temp == NULL) {
  170. D(("out of memory"));
  171. pwd = NULL;
  172. return NULL;
  173. }
  174. sprintf(temp, SECRET_FILE_FORMAT, pwd->pw_dir);
  175. pwd = NULL;
  176. D(("opening key file [%s]", temp));
  177. secrets = fopen(temp, "r");
  178. memset(temp, 0, length_id);
  179. if (secrets == NULL) {
  180. D(("failed to open key file"));
  181. return NULL;
  182. }
  183. length_id = strlen(identity);
  184. temp = malloc(MAX_FILE_LINE_LEN);
  185. for (;;) {
  186. char *secret = NULL;
  187. if (fgets(temp, MAX_FILE_LINE_LEN, secrets) == NULL) {
  188. fclose(secrets);
  189. return NULL;
  190. }
  191. D(("cf[%s][%s]", identity, temp));
  192. if (memcmp(temp, identity, length_id)) {
  193. continue;
  194. }
  195. D(("found entry"));
  196. fclose(secrets);
  197. for (secret=temp+length_id; *secret; ++secret) {
  198. if (!(*secret == ' ' || *secret == '\n' || *secret == '\t')) {
  199. break;
  200. }
  201. }
  202. memmove(temp, secret, MAX_FILE_LINE_LEN-(secret-(temp+length_id)));
  203. secret = temp;
  204. for (; *secret; ++secret) {
  205. if (*secret == ' ' || *secret == '\n' || *secret == '\t') {
  206. break;
  207. }
  208. }
  209. if (*secret) {
  210. *secret = '\0';
  211. }
  212. D(("secret found [%s]", temp));
  213. return temp;
  214. }
  215. /* NOT REACHED */
  216. }
  217. /*
  218. * function to perform the two message authentication process
  219. * (with support for event driven conversation functions)
  220. */
  221. static int auth_sequence(pam_handle_t *pamh,
  222. const struct ps_state_s *old, struct ps_state_s *new)
  223. {
  224. const char *rhostname;
  225. const char *rusername;
  226. int retval;
  227. retval = pam_get_item(pamh, PAM_RUSER, (const void **) &rusername);
  228. if ((retval != PAM_SUCCESS) || (rusername == NULL)) {
  229. D(("failed to obtain an rusername"));
  230. new->state = PS_STATE_DEAD;
  231. return PAM_AUTH_ERR;
  232. }
  233. retval = pam_get_item(pamh, PAM_RHOST, (const void **) &rhostname);
  234. if ((retval != PAM_SUCCESS) || (rhostname == NULL)) {
  235. D(("failed to identify local hostname: ", pam_strerror(pamh, retval)));
  236. new->state = PS_STATE_DEAD;
  237. return PAM_AUTH_ERR;
  238. }
  239. D(("switch on new->state=%d [%s@%s]", new->state, rusername, rhostname));
  240. switch (new->state) {
  241. case PS_STATE_INIT:
  242. {
  243. const char *user = NULL;
  244. retval = pam_get_user(pamh, &user, NULL);
  245. if ((retval == PAM_SUCCESS) && (user == NULL)) {
  246. D(("success but no username?"));
  247. new->state = PS_STATE_DEAD;
  248. retval = PAM_USER_UNKNOWN;
  249. }
  250. if (retval != PAM_SUCCESS) {
  251. if (retval == PAM_CONV_AGAIN) {
  252. retval = PAM_INCOMPLETE;
  253. } else {
  254. new->state = PS_STATE_DEAD;
  255. }
  256. D(("state init failed: %s", pam_strerror(pamh, retval)));
  257. return retval;
  258. }
  259. /* nothing else in this 'case' can be retried */
  260. new->username = strdup(user);
  261. if (new->username == NULL) {
  262. D(("out of memory"));
  263. new->state = PS_STATE_DEAD;
  264. return PAM_BUF_ERR;
  265. }
  266. if (! generate_cookie(new->server_cookie)) {
  267. D(("problem generating server cookie"));
  268. new->state = PS_STATE_DEAD;
  269. return PAM_ABORT;
  270. }
  271. new->current_prompt = NULL;
  272. PAM_BP_RENEW(&new->current_prompt, PAM_BPC_SELECT,
  273. sizeof(PS_AGENT_ID) + strlen(rusername) + 1
  274. + strlen(rhostname) + 1 + 32);
  275. sprintf(PAM_BP_WDATA(new->current_prompt),
  276. PS_AGENT_ID "/%s@%s|%.32s", rusername, rhostname,
  277. new->server_cookie);
  278. /* note, the BP is guaranteed by the spec to be <NUL> terminated */
  279. D(("initialization packet [%s]", PAM_BP_DATA(new->current_prompt)));
  280. /* fall through */
  281. new->state = PS_STATE_PROMPT1;
  282. D(("fall through to state_prompt1"));
  283. }
  284. case PS_STATE_PROMPT1:
  285. {
  286. int i, length;
  287. /* send {secret@here/jdoe@client.host|<s_cookie>} */
  288. retval = converse(pamh, new);
  289. if (retval != PAM_SUCCESS) {
  290. if (retval == PAM_CONV_AGAIN) {
  291. D(("conversation failed to complete"));
  292. return PAM_INCOMPLETE;
  293. } else {
  294. new->state = PS_STATE_DEAD;
  295. return retval;
  296. }
  297. }
  298. if (retval != PAM_SUCCESS) {
  299. D(("failed to read ruser@rhost"));
  300. new->state = PS_STATE_DEAD;
  301. return PAM_AUTH_ERR;
  302. }
  303. /* expect to receive the following {<seqid>|<a_cookie>} */
  304. if (new->current_reply == NULL) {
  305. D(("converstation returned [%s] but gave no reply",
  306. pam_strerror(pamh, retval)));
  307. new->state = PS_STATE_DEAD;
  308. return PAM_CONV_ERR;
  309. }
  310. /* find | */
  311. length = PAM_BP_LENGTH(new->current_reply);
  312. for (i=0; i<length; ++i) {
  313. if (PAM_BP_RDATA(new->current_reply)[i] == '|') {
  314. break;
  315. }
  316. }
  317. if (i >= length) {
  318. D(("malformed response (no |) of length %d", length));
  319. new->state = PS_STATE_DEAD;
  320. return PAM_CONV_ERR;
  321. }
  322. if ((length - ++i) != 32) {
  323. D(("cookie is incorrect length (%d,%d) %d != 32",
  324. length, i, length-i));
  325. new->state = PS_STATE_DEAD;
  326. return PAM_CONV_ERR;
  327. }
  328. /* copy client cookie */
  329. memcpy(new->client_cookie, PAM_BP_RDATA(new->current_reply)+i, 32);
  330. /* generate a prompt that is length(seqid) + length(|) + 32 long */
  331. PAM_BP_RENEW(&new->current_prompt, PAM_BPC_OK, i+32);
  332. /* copy the head of the response prompt */
  333. memcpy(PAM_BP_WDATA(new->current_prompt),
  334. PAM_BP_RDATA(new->current_reply), i);
  335. PAM_BP_RENEW(&new->current_reply, 0, 0);
  336. /* look up the secret */
  337. new->invalid_secret = 0;
  338. if (new->secret_data == NULL) {
  339. char *ruser_rhost;
  340. ruser_rhost = malloc(strlen(rusername)+2+strlen(rhostname));
  341. if (ruser_rhost == NULL) {
  342. D(("out of memory"));
  343. new->state = PS_STATE_DEAD;
  344. return PAM_BUF_ERR;
  345. }
  346. sprintf(ruser_rhost, "%s@%s", rusername, rhostname);
  347. new->secret_data = identify_secret(ruser_rhost, new->username);
  348. memset(ruser_rhost, 0, strlen(ruser_rhost));
  349. free(ruser_rhost);
  350. }
  351. if (new->secret_data == NULL) {
  352. D(("secret not found for user"));
  353. new->invalid_secret = 1;
  354. /* need to make up a secret */
  355. new->secret_data = malloc(32 + 1);
  356. if (new->secret_data == NULL) {
  357. D(("out of memory"));
  358. new->state = PS_STATE_DEAD;
  359. return PAM_BUF_ERR;
  360. }
  361. if (! generate_cookie(new->secret_data)) {
  362. D(("what's up - no fake cookie generated?"));
  363. new->state = PS_STATE_DEAD;
  364. return PAM_ABORT;
  365. }
  366. }
  367. /* construct md5[<client_cookie>|<server_cookie>|<secret_data>] */
  368. if (! create_digest(new->client_cookie, new->server_cookie,
  369. new->secret_data,
  370. PAM_BP_WDATA(new->current_prompt)+i)) {
  371. D(("md5 digesting failed"));
  372. new->state = PS_STATE_DEAD;
  373. return PAM_ABORT;
  374. }
  375. /* prompt2 is now constructed - fall through to send it */
  376. }
  377. case PS_STATE_PROMPT2:
  378. {
  379. /* send {<seqid>|md5[<client_cookie>|<server_cookie>|<secret_data>]} */
  380. retval = converse(pamh, new);
  381. if (retval != PAM_SUCCESS) {
  382. if (retval == PAM_CONV_AGAIN) {
  383. D(("conversation failed to complete"));
  384. return PAM_INCOMPLETE;
  385. } else {
  386. new->state = PS_STATE_DEAD;
  387. return retval;
  388. }
  389. }
  390. /* After we complete this section, we should not be able to
  391. recall this authentication function. So, we force all
  392. future calls into the weeds. */
  393. new->state = PS_STATE_DEAD;
  394. /* expect reply:{md5[<secret_data>|<server_cookie>|<client_cookie>]} */
  395. {
  396. int cf;
  397. char expectation[33];
  398. if (!create_digest(new->secret_data, new->server_cookie,
  399. new->client_cookie, expectation)) {
  400. new->state = PS_STATE_DEAD;
  401. return PAM_ABORT;
  402. }
  403. cf = strcmp(expectation, PAM_BP_RDATA(new->current_reply));
  404. memset(expectation, 0, sizeof(expectation));
  405. if (cf || new->invalid_secret) {
  406. D(("failed to authenticate"));
  407. return PAM_AUTH_ERR;
  408. }
  409. }
  410. D(("correctly authenticated :)"));
  411. return PAM_SUCCESS;
  412. }
  413. default:
  414. new->state = PS_STATE_DEAD;
  415. case PS_STATE_DEAD:
  416. D(("state is currently dead/unknown"));
  417. return PAM_AUTH_ERR;
  418. }
  419. fprintf(stderr, "pam_secret: this should not be reached\n");
  420. return PAM_ABORT;
  421. }
  422. static void clean_data(pam_handle_t *pamh, void *datum, int error_status)
  423. {
  424. struct ps_state_s *data = datum;
  425. D(("liberating datum=%p", datum));
  426. if (data) {
  427. D(("renew prompt"));
  428. PAM_BP_RENEW(&data->current_prompt, 0, 0);
  429. D(("renew reply"));
  430. PAM_BP_RENEW(&data->current_reply, 0, 0);
  431. D(("overwrite datum"));
  432. memset(data, 0, sizeof(struct ps_state_s));
  433. D(("liberate datum"));
  434. free(data);
  435. }
  436. D(("done."));
  437. }
  438. /*
  439. * front end for the authentication function
  440. */
  441. int pam_sm_authenticate(pam_handle_t *pamh, int flags,
  442. int argc, const char **argv)
  443. {
  444. int retval;
  445. struct ps_state_s *new_data;
  446. const struct ps_state_s *old_data;
  447. D(("called"));
  448. new_data = calloc(1, sizeof(struct ps_state_s));
  449. if (new_data == NULL) {
  450. D(("out of memory"));
  451. return PAM_BUF_ERR;
  452. }
  453. new_data->retval = PAM_SUCCESS;
  454. retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
  455. if (retval == PAM_SUCCESS) {
  456. new_data->state = old_data->state;
  457. memcpy(new_data->server_cookie, old_data->server_cookie, 32);
  458. memcpy(new_data->client_cookie, old_data->client_cookie, 32);
  459. if (old_data->username) {
  460. new_data->username = strdup(old_data->username);
  461. }
  462. if (old_data->secret_data) {
  463. new_data->secret_data = strdup(old_data->secret_data);
  464. }
  465. if (old_data->current_prompt) {
  466. int length;
  467. length = PAM_BP_LENGTH(old_data->current_prompt);
  468. PAM_BP_RENEW(&new_data->current_prompt,
  469. PAM_BP_CONTROL(old_data->current_prompt), length);
  470. PAM_BP_FILL(new_data->current_prompt, 0, length,
  471. PAM_BP_RDATA(old_data->current_prompt));
  472. }
  473. /* don't need to duplicate current_reply */
  474. } else {
  475. old_data = NULL;
  476. new_data->state = PS_STATE_INIT;
  477. }
  478. D(("call auth_sequence"));
  479. new_data->retval = auth_sequence(pamh, old_data, new_data);
  480. D(("returned from auth_sequence"));
  481. retval = pam_set_data(pamh, PS_STATE_ID, new_data, clean_data);
  482. if (retval != PAM_SUCCESS) {
  483. D(("unable to store new_data"));
  484. } else {
  485. retval = new_data->retval;
  486. }
  487. old_data = new_data = NULL;
  488. D(("done (%d)", retval));
  489. return retval;
  490. }
  491. /*
  492. * front end for the credential setting function
  493. */
  494. #define AUTH_SESSION_TICKET_ENV_FORMAT "AUTH_SESSION_TICKET="
  495. int pam_sm_setcred(pam_handle_t *pamh, int flags,
  496. int argc, const char **argv)
  497. {
  498. int retval;
  499. const struct ps_state_s *old_data;
  500. D(("called"));
  501. /* XXX - need to pay attention to the various flavors of call */
  502. /* XXX - need provide an option to turn this feature on/off: if
  503. other modules want to supply an AUTH_SESSION_TICKET, we should
  504. leave it up to the admin which module dominiates. */
  505. retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
  506. if (retval != PAM_SUCCESS) {
  507. D(("no data to base decision on"));
  508. return PAM_AUTH_ERR;
  509. }
  510. /*
  511. * If ok, export a derived shared secret session ticket to the
  512. * client's PAM environment - the ticket has the form
  513. *
  514. * AUTH_SESSION_TICKET =
  515. * md5[<server_cookie>|<secret_data>|<client_cookie>]
  516. *
  517. * This is a precursor to supporting a spoof resistant trusted
  518. * path mechanism. This shared secret ticket can be used to add
  519. * a hard-to-guess checksum to further authentication data.
  520. */
  521. retval = old_data->retval;
  522. if (retval == PAM_SUCCESS) {
  523. char envticket[sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)+32];
  524. memcpy(envticket, AUTH_SESSION_TICKET_ENV_FORMAT,
  525. sizeof(AUTH_SESSION_TICKET_ENV_FORMAT));
  526. if (! create_digest(old_data->server_cookie, old_data->secret_data,
  527. old_data->client_cookie,
  528. envticket+sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)-1
  529. )) {
  530. D(("unable to generate a digest for session ticket"));
  531. return PAM_ABORT;
  532. }
  533. D(("putenv[%s]", envticket));
  534. retval = pam_putenv(pamh, envticket);
  535. memset(envticket, 0, sizeof(envticket));
  536. }
  537. old_data = NULL;
  538. D(("done (%d)", retval));
  539. return retval;
  540. }