/* $Id: ckpasswd.c 7565 2006-08-28 02:42:54Z eagle $ ** ** The default username/password authenticator. ** ** This program is intended to be run by nnrpd and handle usernames and ** passwords. It can authenticate against a regular flat file (the type ** managed by htpasswd), a DBM file, the system password file or shadow file, ** or PAM. */ /* Used for unused parameters to silence gcc warnings. */ #define UNUSED __attribute__((__unused__)) /* Make available the bool type. */ #if INN_HAVE_STDBOOL_H # include #else # undef true # undef false # define true (1) # define false (0) # ifndef __cplusplus # define bool int # endif #endif /* INN_HAVE_STDBOOL_H */ #include #include #include #include #include #include #include #define DB_DBM_HSEARCH 1 #include #define OPT_DBM "d:" #if HAVE_GETSPNAM # include # define OPT_SHADOW "s" #else # define OPT_SHADOW "" #endif #include "messages.h" #include "xmalloc.h" #include /* Holds the authentication information from nnrpd. */ struct auth_info { char *username; char *password; }; /* ** The PAM conversation function. ** ** Since we already have all the information and can't ask the user ** questions, we can't quite follow the real PAM protocol. Instead, we just ** return the password in response to every question that PAM asks. There ** appears to be no generic way to determine whether the message in question ** is indeed asking for the password.... ** ** This function allocates an array of struct pam_response to return to the ** PAM libraries that's never freed. For this program, this isn't much of an ** issue, since it will likely only be called once and then the program will ** exit. This function uses malloc and strdup instead of xmalloc and xstrdup ** intentionally so that the PAM conversation will be closed cleanly if we ** run out of memory rather than simply terminated. ** ** appdata_ptr contains the password we were given. */ static int pass_conv(int num_msg, const struct pam_message **msgm UNUSED, struct pam_response **response, void *appdata_ptr) { int i; *response = malloc(num_msg * sizeof(struct pam_response)); if (*response == NULL) return PAM_CONV_ERR; for (i = 0; i < num_msg; i++) { (*response)[i].resp = strdup((char *)appdata_ptr); (*response)[i].resp_retcode = 0; } return PAM_SUCCESS; } /* ** Authenticate a user via PAM. ** ** Attempts to authenticate a user with PAM, returning true if the user ** successfully authenticates and false otherwise. Note that this function ** doesn't attempt to handle any remapping of the authenticated user by the ** PAM stack, but just assumes that the authenticated user was the same as ** the username given. ** ** Right now, all failures are handled via die. This may be worth revisiting ** in case we want to try other authentication methods if this fails for a ** reason other than the system not having PAM support. */ static bool auth_pam(const char *username, char *password) { pam_handle_t *pamh; struct pam_conv conv; int status; conv.conv = pass_conv; conv.appdata_ptr = password; status = pam_start("nnrpd", username, &conv, &pamh); if (status != PAM_SUCCESS) die("pam_start failed: %s", pam_strerror(pamh, status)); status = pam_authenticate(pamh, PAM_SILENT); if (status != PAM_SUCCESS) die("pam_authenticate failed: %s", pam_strerror(pamh, status)); status = pam_acct_mgmt(pamh, PAM_SILENT); if (status != PAM_SUCCESS) die("pam_acct_mgmt failed: %s", pam_strerror(pamh, status)); status = pam_end(pamh, status); if (status != PAM_SUCCESS) die("pam_end failed: %s", pam_strerror(pamh, status)); /* If we get to here, the user successfully authenticated. */ return true; } /* ** Try to get a password out of a dbm file. The dbm file should have the ** username for the key and the crypted password as the value. The crypted ** password, if found, is returned as a newly allocated string; otherwise, ** NULL is returned. */ #if !(defined(HAVE_DBM) || defined(HAVE_BDB_DBM)) static char * password_dbm(char *user UNUSED, const char *file UNUSED) { return NULL; } #else static char * password_dbm(char *name, const char *file) { datum key, value; DBM *database; char *password; database = dbm_open(file, O_RDONLY, 0600); if (database == NULL) return NULL; key.dptr = name; key.dsize = strlen(name); value = dbm_fetch(database, key); if (value.dptr == NULL) { dbm_close(database); return NULL; } password = xmalloc(value.dsize + 1); strlcpy(password, value.dptr, value.dsize + 1); dbm_close(database); return password; } #endif /* HAVE_DBM || HAVE_BDB_DBM */ /* ** Try to get a password out of the system /etc/shadow file. The crypted ** password, if found, is returned as a newly allocated string; otherwise, ** NULL is returned. */ #if !HAVE_GETSPNAM static char * password_shadow(const char *user UNUSED) { return NULL; } #else static char * password_shadow(const char *user) { struct spwd *spwd; spwd = getspnam(user); if (spwd != NULL) return xstrdup(spwd->sp_pwdp); return NULL; } #endif /* HAVE_GETSPNAM */ /* ** Try to get a password out of the system password file. The crypted ** password, if found, is returned as a newly allocated string; otherwise, ** NULL is returned. */ static char * password_system(const char *username) { struct passwd *pwd; pwd = getpwnam(username); if (pwd != NULL) return xstrdup(pwd->pw_passwd); return NULL; } /* ** Try to get the name of a user's primary group out of the system group ** file. The group, if found, is returned as a newly allocated string; ** otherwise, NULL is returned. If the username is not found, NULL is ** returned. */ static char * group_system(const char *username) { struct passwd *pwd; struct group *gr; pwd = getpwnam(username); if (pwd == NULL) return NULL; gr = getgrgid(pwd->pw_gid); if (gr == NULL) return NULL; return xstrdup(gr->gr_name); } /* ** Output username (and group, if desired) in correct return format. */ static void output_user(const char *username, bool wantgroup) { if (wantgroup) { char *group = group_system(username); if (group == NULL) die("group info for user %s not available", username); printf("User:%s@%s\n", username, group); } else printf("User:%s\n", username); } /* ** Main routines. ** ** We handle the variences between systems with #if blocks above, so that ** this code can look fairly clean. */ int main(int argc, char *argv[]) { enum authtype { AUTH_NONE, AUTH_SHADOW, AUTH_FILE, AUTH_DBM }; int opt; enum authtype type = AUTH_NONE; bool wantgroup = false; const char *filename = NULL; struct auth_info *authinfo = NULL; char *password = NULL; //message_program_name = "ckpasswd"; while ((opt = getopt(argc, argv, "gf:u:p:" OPT_DBM OPT_SHADOW)) != -1) { switch (opt) { case 'g': if (type == AUTH_DBM || type == AUTH_FILE) die("-g option is incompatible with -d or -f"); wantgroup = true; break; case 'd': if (type != AUTH_NONE) die("only one of -s, -f, or -d allowed"); if (wantgroup) die("-g option is incompatible with -d or -f"); type = AUTH_DBM; filename = optarg; break; case 'f': if (type != AUTH_NONE) die("only one of -s, -f, or -d allowed"); if (wantgroup) die("-g option is incompatible with -d or -f"); type = AUTH_FILE; filename = optarg; break; case 's': if (type != AUTH_NONE) die("only one of -s, -f, or -d allowed"); type = AUTH_SHADOW; break; case 'u': if (authinfo == NULL) { authinfo = xmalloc(sizeof(struct auth_info)); authinfo->password = NULL; } authinfo->username = optarg; break; case 'p': if (authinfo == NULL) { authinfo = xmalloc(sizeof(struct auth_info)); authinfo->username = NULL; } authinfo->password = optarg; break; default: exit(1); } } if (argc != optind) die("extra arguments given"); if (authinfo != NULL && authinfo->username == NULL) die("-u option is required if -p option is given"); if (authinfo != NULL && authinfo->password == NULL) die("-p option is required if -u option is given"); // /* Unless a username or password was given on the command line, assume // we're being run by nnrpd. */ // if (authinfo == NULL) // authinfo = get_auth_info(stdin); // if (authinfo == NULL) // die("no authentication information from nnrpd"); // if (authinfo->username[0] == '\0') // die("null username"); /* Run the appropriate authentication routines. */ switch (type) { case AUTH_SHADOW: password = password_shadow(authinfo->username); if (password == NULL) password = password_system(authinfo->username); break; // case AUTH_FILE: // password = password_file(authinfo->username, filename); // break; case AUTH_DBM: password = password_dbm(authinfo->username, filename); break; case AUTH_NONE: if (auth_pam(authinfo->username, authinfo->password)) { output_user(authinfo->username, wantgroup); exit(0); } password = password_system(authinfo->username); break; } if (password == NULL) die("user %s unknown", authinfo->username); if (strcmp(password, crypt(authinfo->password, password)) != 0) die("invalid password for user %s", authinfo->username); /* The password matched. */ output_user(authinfo->username, wantgroup); exit(0); }