I am trying to get data into a process invoked via ssh by adding it the supplied username, e.g.

ssh user@smuggledata@host

I've written a PAM module, however

  1. sshd has already decided the user is bad before invoking PAM
  2. although I have changed the PAM_USER, subsequent modules are still authenticating against the submitted username.

I first tried running the code with auth requisite mymodule.so and the code in pam_sm_authenticate(). Although the code fired - it did not work:

May  1 22:40:30 animal sshd[3827]: Invalid user colin@example from
May  1 22:40:30 animal sshd[3827]: input_userauth_request: invalid user colin@example [preauth]
May  1 22:40:35 animal pam_pat[3827]: Retrieved username colin@example
May  1 22:40:35 animal pam_pat[3827]: checking char c against 3 dividers
May  1 22:40:35 animal pam_pat[3827]: checking char o against 3 dividers
May  1 22:40:35 animal pam_pat[3827]: checking char l against 3 dividers
May  1 22:40:35 animal pam_pat[3827]: checking char i against 3 dividers
May  1 22:40:35 animal pam_pat[3827]: checking char n against 3 dividers
May  1 22:40:35 animal pam_pat[3827]: checking char @ against 3 dividers
May  1 22:40:35 animal pam_pat[3827]: Found divider @
May  1 22:40:35 animal pam_pat[3827]: user=colin, data=example
May  1 22:40:35 animal sshd[3827]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=  user=colin
May  1 22:40:37 animal sshd[3827]: Failed password for invalid user colin@example from port 43998 ssh2
May  1 22:41:28 animal sshd[3827]: Connection closed by port 43998 [preauth]

Note that when execution reaches pam_unix authentication fails but the user is reported as the transformed value in PAM_USER. The password was correct, hence pam_unix was presumably not authenticating against PAM_USER.

I also tried with account requisite mymodule.so and the code in pam_sm_acct_mgmt() - but the code was not invoked:

May  1 22:57:10 animal sshd[4105]: Invalid user colin@exmple from
May  1 22:57:10 animal sshd[4105]: input_userauth_request: invalid user colin@exmple [preauth]
May  1 22:57:16 animal sshd[4105]: pam_unix(sshd:auth): check pass; user unknown
May  1 22:57:16 animal sshd[4105]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=
May  1 22:57:18 animal sshd[4105]: Failed password for invalid user colin@exmple from port 44118 ssh2
May  1 22:57:27 animal sshd[4105]: Connection closed by port 44118 [preauth]

Looking at the source code for pam_unix it retrieves the username from pam_get_user() which apparently points to something other than PAM_USER.

How do I change the value which is subsequently user for authentication?

  • how do I ensure this happens before any other processing of the username occurs?
  • is there a better way to do it than to write back to the pointer returned by pam_get_user() ?

update The code (with added pam_get_user()):

    char *submitted_name;
    char work_bufr[PAT_BUFR];
    char log_entry[PAT_BUFR];
    int an_int;
    char dividers[]="@%+";
    int num_dividers;
    char *cursor;
    char divider_found[]="0";
    const char *debug_user;

    openlog("pam_pat",  LOG_CONS | LOG_PID | LOG_NDELAY, LOG_AUTH);
    /* retrieve a copy of the submitted username */
    if (PAM_SUCCESS != pam_get_item(pamh, PAM_USER, (void *) &submitted_name) || !(submitted_name)) {
      syslog (LOG_ERR, "Failed to retrieve username from pam");
    syslog (LOG_ERR, "Retrieved username %s", submitted_name);
    strncpy(work_bufr, submitted_name, PAT_BUFR);
    /* search for dividers and split string */
    while (--an_int && '\0'!=*cursor) {
       syslog(LOG_ERR, "checking char %c against %d dividers", (int)*cursor, num_dividers);
       for (int x=0; x<num_dividers; x++) {
          if (*cursor==dividers[x]) {
             syslog(LOG_ERR, "Found divider %c", *cursor);
             if (PAM_SUCCESS==pam_set_item(pamh, PAM_USER, submitted_name)) {
                 syslog (LOG_ERR, "user=%s, data=%s",submitted_name,cursor);
                 setenv("PAM_PAT_DIVDR", divider_found, 1);
                 setenv("PAM_PAT_DATA", cursor, 1);
                 if (PAM_SUCCESS == pam_get_user(pamh, &debug_user, NULL)) {
                      syslog (LOG_ERR, "pam_get_user() found %s", debug_user);
             } else {
                 syslog (LOG_ERR, "Failed to update username");
    syslog (LOG_ERR, "Extended username not found.");

The man page for pam_get_user(3) says it gets its value from the user obtained by pam_start(3). That function in turn says that elements in its structure can be set using pam_set_item(3). The man page for this third function provides an explicit reference to changing the username being tested for authentication.

The pam_set_item function allows applications and PAM service modules to access and to update PAM informations of item_type. For this a copy of the object pointed to by the item argument is created. The following item_types are supported [...]

PAM_USER The username of the entity under whose identity service will be given. That is, following authentication, PAM_USER identifies the local entity that gets to use the service. Note, this value can be mapped from something (eg., "anonymous") to something else (eg. "guest119") by any module in the PAM stack. As such an application should consult the value of PAM_USER after each call to a PAM function.

| improve this answer | |
  • The log entries above seem to prove this is not the case; when pam_unix runs it is not accepting the password for the modified username, and sshd is already reporting invalid user before either module fires. If you look at the code I linked to, pam_get_user() is being invoked in pam_unix after my module has amended PAM_USER and called pam_set_item() successfully. – symcbean May 1 '17 at 22:47
  • @symcbean I don't see anywhere in your question that you used pam_set_item to change the authenticated username, only that you changed PAM_USER. That sshd is reporting the user invalid before even getting to pam is not something I've addressed here. Tomorrow I can see if there's a solution to that. – roaima May 1 '17 at 22:59
  • I did say that I had changed PAM_USER (which is only possible with pam_set_item()). I added an additional call to pam_get_user() after pam_set_item() which showed that the username had changed - but presumably not in the order I expected since pam_unix still reports authentication failed but for the changed username – symcbean May 1 '17 at 23:04

input_userauth_request() is a function within sshd which prompts for the username.

Fro reading about pam_ldap (where the username being authenticated is not necessarily known to the local system) the "sshd[xxx]: input_userauth_request: invalid user ..." message pops up when the LDAP NSS lib is not installed or not configured. From this I infer that sshd first attempts to validate the username before handing off to PAM (WTF?). So in order to avoid this initial error, I need to write a NSS module (presumably implementing getpwnam() but I expect I'll have to take sshd apart to find out how it ticks).


(and I'm still not confident this will fix the problem)

| improve this answer | |

Old thread, but I wanted to put together a complete answer.

I had a similar need, and I did pretty much used what is mentioned here and stitched together a solution.

  1. libnss-ato - this is needed so that a non existent user is still allowed (I was using this before as well)

  2. I created a simple PAM module (mostly dummy functions returning PAM_SUCCESS) except for the following:

      static char*
      _find_base_user (const char *user)
        char        *p             = NULL;
        static char  user_base[64] = {0};

        if ((p = strchr(user, '@'))) {
            memset(user_base, 0, 64);
            strncpy(user_base, user, (int)(p - user));
            return user_base;

        return NULL;

      pam_sm_authenticate (pam_handle_t  *pamh,
                          int            flags,
                          int            argc,
                          const char   **argv)
        int         ret      = 0;
        const char *user;
        char       *user_pfx = NULL;

        ret = pam_get_user(pamh, &user, NULL);
        if (ret != PAM_SUCCESS || user == NULL) {
            pam_syslog(pamh, LOG_ERR, "no user specified");
        } else {
            pam_syslog(pamh, LOG_DEBUG, "user = %s", user);
        user_pfx = _find_base_user(user);
        if (user_pfx) {
            pam_syslog(pamh, LOG_DEBUG, "user base name: %s", user_pfx);
        } else {
            pam_syslog(pamh, LOG_ERR, "no separator in user name found");
            return PAM_SUCCESS;

        ret = pam_set_item(pamh, PAM_USER, (const void *)user_pfx);
        if (ret!= PAM_SUCCESS) {
            pam_syslog(pamh, LOG_ERR, "user resetting failed");
            return PAM_USER_UNKNOWN;

        return PAM_SUCCESS;

Lets call this pam_changeuser.so, now I added this at the top of my PAM stack (first module)

 auth required pam_changeuser.so

With this, I am able to smuggle some additional-data with the username!

Hope this helps someone.


| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.