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