// Copyright (c) 1998 Red Hat Software, Inc. // Authors: Michael K. Johnson // Jacques Gelinas // Debian changes by Stefan Gybas #include #include #include #include "debian.h" #include "debian.m" extern "C" { #include } static HELP_FILE help_passwd ("debian", "passwd"); static const char *tbargs[]={"user","newpassword","islocked","domain",NULL}; static MESSAGE_DEF msg_chgpasswd ("chgpasswd",tbargs); static SSTRING title; static const char *intro; static bool cancel = false; static char *last[2]; // Remember user input to update other password static const char *suggested=NULL; // Auto-generated password to send to PAM static void (*fct_html) (DIALOG &dia, bool intro); static int dia_conv ( int num_msg, const struct pam_message **msgm, struct pam_response **response, void *) // appdata_ptr { struct pam_response *reply = (struct pam_response*) calloc (num_msg , sizeof(struct pam_response)); *response = reply; if (reply == NULL) return PAM_CONV_ERR; if (cancel) return PAM_CONV_ERR; int lookup[num_msg]; memset (lookup,-1,sizeof(lookup)); DIALOG dia; dia.settype (DIATYPE_POPUP); SSTRINGS resp; for (int count = 0; count < num_msg; count++) { int style = msgm[count]->msg_style; switch(style) { case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: { SSTRING *s = new SSTRING; lookup[resp.getnb()] = count; resp.add (s); if (style == PAM_PROMPT_ECHO_OFF){ dia.newf_pass (msgm[count]->msg, *s); }else{ dia.newf_str (msgm[count]->msg, *s); } } break; case PAM_ERROR_MSG: // we don't touch resp xconf_error ("%s",msgm[count]->msg); break; case PAM_TEXT_INFO: // PAM unfortunatly is really minded for tty input // calling us one question at a time and sending one // notice at a time. Or is it PAM. Maybe pam_pwdb does // this since PAM can provide in a single run all the prompts // and all the question. But for now, the only notice // generated is annoying. This is why the next line is // commented. //xconf_notice ("%s",msgm[count]->msg); break; default: fprintf(stderr, "erroneous PAM conversation (%d)\n",style); return PAM_CONV_ERR; } } cancel = false; if (dia.getnb()>0){ int nof = 0; /* #Specification: html mode / trickery Linuxconf consider that two dialogs with the same title are indeed the same dialog. See html.cc, function DIALOG::html_draw_top() for explanation. In this case (changing a password with PAM), we are repeatedly calling the same dialog again and again (at least twice in fact). But it is not the same dialog. It is simply PAM which is asking for a password, then a confirmation and so on. But here we are always using the same dialog title. So this confuse linuxconf. By appending a blank to the title each time PAM calls us, we differentiate the various dialog instances. The html mode has been transparent to all parts of linuxconf so far... :-( */ title.append (" "); if (suggested != NULL || dia.edit (title.get(),intro ,help_passwd, nof) == MENU_ACCEPT) { for (int count = 0; count < resp.getnb(); count++) { int i = lookup[count]; if (i != -1){ /* #Specification: PAM / synchronising other files When using pam, Linuxconf is a little blind about passwords. It sets dialogs but has no clue what they are for. Ultimatly, PAM may ask the user to provide 3 different passwords for example (one for intranet use, one for extranet and one for samba). While PAM is quite flexibie, in 99.9% of the case, PAM is used to update a normal passwd database (either /etc/passwd or /etc/shadow) with a single password. Linuxconf is using a trick to collect this password in non-encrypted form, so it can be passed to other modules, such as the samba module. While synchronising the /etc/passwd and /etc/smbpasswd databases is a task which should be handled by samba, it is not currently done. So this trick is quite useful. The trick is to remember the last two input from the user when changing a password. If those two inputs are the same, then the message is sent to other modules so they can update their own password database. */ const char *pt = resp.getitem(count)->get(); free (last[1]); last[1] = last[0]; last[0] = strdup(pt); reply[i].resp = strdup (pt); reply[i].resp_retcode = 0; } } } else { cancel = true; return PAM_CONV_ERR; } } return PAM_SUCCESS; } static struct pam_conv conv = { dia_conv, NULL }; /* Authenticate a user */ int pam_check_pass( const char *user) { pam_handle_t *pamh = NULL; cancel = false; title.setfrom (MSG_U(T_AUTHENTICATE,"Authentication")); char tmp[200]; snprintf (tmp,sizeof(tmp)-1,MSG_U(I_AUTHREQ ,"Authentication required\n" "for user %s"),user); intro = tmp; int retval = pam_start ("linuxconf", user, &conv, &pamh); if (retval == PAM_SUCCESS) { retval = pam_authenticate(pamh, 0); if (retval == PAM_SUCCESS){ retval = pam_acct_mgmt(pamh, 0); } } if (pamh != NULL) pam_end(pamh, PAM_SUCCESS); if (retval != PAM_SUCCESS) return cancel ? -1 : 0; else return 1; } int pam_change_pass( const char *user, bool preauthenticated, const char *_suggested, void (*_fct_html) (DIALOG &dia, bool intro)) { fct_html = _fct_html; suggested = NULL; if (_suggested != NULL && _suggested[0] != '\0') suggested = _suggested; cancel = false; title.setfrom (MSG_U(T_NEWPASS,"Changing password")); char tmp[200]; snprintf (tmp,sizeof(tmp)-1,MSG_U(I_NEWPASS ,"Changing the password for user %s") ,user); intro = tmp; pam_handle_t *pamh = NULL; // use the passwd service so that linuxconf automatically // does the right thing when people change passwd's config int retval = pam_start ("passwd", user, &conv, &pamh); /* PAM authenticate (ask the user to provide the old password) if the real UID is != 0. This situation occurs when linuxconf is run setuid. In this case, if we are here, linuxconf has already made sure we have enough privilege to do the password change. */ uid_t old_uid = getuid(); if (old_uid != 0){ if (preauthenticated) setuid (0); }else if (!preauthenticated) { // PAM normally does authentication if necessary, but // if real uid == 0 and we still want to authenticate we // need to do it ourselves. if (retval == PAM_SUCCESS) { retval = pam_authenticate(pamh, 0); if (retval == PAM_SUCCESS){ retval = pam_acct_mgmt(pamh, 0); } } } if (retval == PAM_SUCCESS) { retval = pam_chauthtok(pamh, 0); } if (pamh != NULL) pam_end(pamh, PAM_SUCCESS); setreuid (old_uid,0); // Return to the original state int ret = -1; if (retval == PAM_SUCCESS){ ret = 0; if (last[1] != NULL && last[0] != NULL && strcmp(last[0],last[1])==0){ const char *tb[]={ user,last[0],"0","/" }; module_sendmessage (msg_chgpasswd,4,tb); } } free (last[1]); free (last[0]); last[0] = last[1] = NULL; suggested = NULL; fct_html = NULL; return ret; } /* Static variables used to communicate between the conversation function * and the server_login function */ static const char *PAM_password; /* hackish PAM conversation function * Here we assume that echo off means password. */ static int hack_conv ( int num_msg, const struct pam_message **msg, struct pam_response **resp, void *) { #define COPY_STRING(s) (s) ? strdup(s) : (char*)NULL struct pam_response *reply = (struct pam_response*)malloc(sizeof(struct pam_response) * num_msg); if (!reply) return PAM_CONV_ERR; for (int replies = 0; replies < num_msg; replies++) { switch (msg[replies]->msg_style) { case PAM_PROMPT_ECHO_OFF: reply[replies].resp_retcode = PAM_SUCCESS; reply[replies].resp = COPY_STRING(PAM_password); /* PAM frees resp */ break; case PAM_TEXT_INFO: /* fall through */ case PAM_ERROR_MSG: /* ignore it, but pam still wants a NULL response... */ reply[replies].resp_retcode = PAM_SUCCESS; reply[replies].resp = NULL; break; case PAM_PROMPT_ECHO_ON: /* fall through */ default: /* Must be an error of some sort... */ free (reply); return PAM_CONV_ERR; } } *resp = reply; return PAM_SUCCESS; } static struct pam_conv hack_conversation = { &hack_conv, NULL }; int pam_check_pair( const char *user, const char *pass) { int retval; pam_handle_t *pamh = NULL; PAM_password = pass; retval = pam_start ("linuxconf", user, &hack_conversation, &pamh); if (retval == PAM_SUCCESS) { retval = pam_authenticate(pamh, 0); if (retval == PAM_SUCCESS){ retval = pam_acct_mgmt(pamh, 0); } } if (pamh != NULL) pam_end(pamh, PAM_SUCCESS); if (retval != PAM_SUCCESS) return 0; else return 1; }