| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| /* |
| * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/errno.h> |
| #include <pwd.h> |
| #include <unistd.h> |
| #include <syslog.h> |
| |
| #include <netdb.h> |
| |
| #include <rpc/rpc.h> |
| #include <rpcsvc/yppasswd.h> |
| #include <rpcsvc/ypclnt.h> |
| #include <rpcsvc/yp_prot.h> |
| |
| #include "passwdutil.h" |
| |
| int nis_getattr(char *name, attrlist *item, pwu_repository_t *rep); |
| int nis_getpwnam(char *name, attrlist *items, pwu_repository_t *rep, |
| void **buf); |
| int nis_update(attrlist *items, pwu_repository_t *rep, void *buf); |
| int nis_putpwnam(char *name, char *oldpw, pwu_repository_t *rep, void *buf); |
| int nis_user_to_authenticate(char *user, pwu_repository_t *rep, |
| char **auth_user, int *privileged); |
| |
| /* |
| * nis function pointer table, used by passwdutil_init to initialize |
| * the global Repository-OPerations table "rops" |
| */ |
| struct repops nis_repops = { |
| NULL, /* checkhistory */ |
| nis_getattr, |
| nis_getpwnam, |
| nis_update, |
| nis_putpwnam, |
| nis_user_to_authenticate, |
| NULL, /* lock */ |
| NULL /* unlock */ |
| }; |
| |
| /* |
| * structure used to keep state between get/update/put calls |
| */ |
| typedef struct { |
| char *domain; |
| char *master; |
| char *scratch; |
| int scratchlen; |
| char *c2scratch; |
| int c2scratchlen; |
| struct passwd *pwd; |
| } nisbuf_t; |
| |
| /* |
| * Are we a 'privileged' process? Yes if we are running on the |
| * NIS server AND we are root... |
| */ |
| int |
| nis_privileged(nisbuf_t *nisbuf) |
| { |
| char thishost[MAXHOSTNAMELEN]; |
| if (gethostname(thishost, sizeof (thishost)) == -1) { |
| syslog(LOG_ERR, "passwdutil.so: Can't get hostname"); |
| return (0); |
| } |
| |
| if (strcmp(nisbuf->master, thishost) != 0) |
| return (0); |
| |
| /* We're running on the NIS server. */ |
| return (getuid() == 0); |
| } |
| |
| /* |
| * nis_to_pwd() |
| * |
| * convert password-entry-line to "struct passwd" |
| */ |
| void |
| nis_to_pwd(char *nis, struct passwd *pwd) |
| { |
| pwd->pw_name = strsep(&nis, ":"); |
| pwd->pw_passwd = strsep(&nis, ":"); |
| pwd->pw_uid = atoi(strsep(&nis, ":")); |
| pwd->pw_gid = atoi(strsep(&nis, ":")); |
| pwd->pw_gecos = strsep(&nis, ":"); |
| pwd->pw_dir = strsep(&nis, ":"); |
| pwd->pw_shell = nis; |
| if (pwd->pw_shell[0]) |
| pwd->pw_shell[strlen(pwd->pw_shell)-1] = '\0'; |
| } |
| |
| /* |
| * nis_user_to_authenticate(name, rep, auth_user, privileged) |
| * |
| */ |
| /*ARGSUSED*/ |
| int |
| nis_user_to_authenticate(char *user, pwu_repository_t *rep, |
| char **auth_user, int *privileged) |
| { |
| nisbuf_t *buf = NULL; |
| int res; |
| attrlist attr_tmp[1]; |
| uid_t uid; |
| |
| /* |
| * special NIS case: don't bother to get "root" from NIS |
| */ |
| if (strcmp(user, "root") == 0) |
| return (PWU_NOT_FOUND); |
| |
| attr_tmp[0].type = ATTR_UID; |
| attr_tmp[0].next = NULL; |
| |
| res = nis_getpwnam(user, &attr_tmp[0], rep, (void **)&buf); |
| |
| if (res != PWU_SUCCESS) |
| return (res); |
| |
| if (nis_privileged(buf)) { |
| *privileged = 1; |
| *auth_user = NULL; |
| res = PWU_SUCCESS; |
| } else { |
| uid = getuid(); |
| |
| *privileged = (uid == (uid_t)0); |
| |
| /* root, or user herself can change attributes */ |
| if (uid == 0 || uid == buf->pwd->pw_uid) { |
| *auth_user = strdup(user); |
| res = PWU_SUCCESS; |
| } else { |
| res = PWU_DENIED; |
| } |
| } |
| |
| /* |
| * Do not release buf->domain. |
| * It's been set by yp_get_default_domain() |
| * and must not be freed. |
| * See man page yp_get_default_domain(3NSL) |
| * for details. |
| */ |
| if (buf->master) |
| free(buf->master); |
| if (buf->scratch) |
| free(buf->scratch); |
| if (buf->c2scratch) |
| free(buf->c2scratch); |
| free(buf->pwd); |
| free(buf); |
| |
| return (res); |
| } |
| |
| |
| /* |
| * nis_getattr(name, items, rep) |
| * |
| * get account attributes specified in 'items' |
| */ |
| int |
| nis_getattr(char *name, attrlist *items, pwu_repository_t *rep) |
| { |
| nisbuf_t *nisbuf = NULL; |
| struct passwd *pw; |
| attrlist *w; |
| int res; |
| |
| res = nis_getpwnam(name, items, rep, (void **)&nisbuf); |
| if (res != PWU_SUCCESS) |
| return (res); |
| |
| pw = nisbuf->pwd; |
| |
| for (w = items; w != NULL; w = w->next) { |
| switch (w->type) { |
| case ATTR_NAME: |
| if ((w->data.val_s = strdup(pw->pw_name)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_COMMENT: |
| if ((w->data.val_s = strdup(pw->pw_comment)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_GECOS: |
| if ((w->data.val_s = strdup(pw->pw_gecos)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_HOMEDIR: |
| if ((w->data.val_s = strdup(pw->pw_dir)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_SHELL: |
| if ((w->data.val_s = strdup(pw->pw_shell)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_PASSWD: |
| case ATTR_PASSWD_SERVER_POLICY: |
| if ((w->data.val_s = strdup(pw->pw_passwd)) == NULL) |
| res = PWU_NOMEM; |
| break; |
| case ATTR_REP_NAME: |
| if ((w->data.val_s = strdup("nis")) == NULL) |
| res = PWU_NOMEM; |
| break; |
| |
| /* integer values */ |
| case ATTR_UID: |
| w->data.val_i = nisbuf->pwd->pw_uid; |
| break; |
| case ATTR_GID: |
| w->data.val_i = nisbuf->pwd->pw_gid; |
| break; |
| case ATTR_LSTCHG: |
| case ATTR_MIN: |
| case ATTR_MAX: |
| case ATTR_WARN: |
| case ATTR_INACT: |
| case ATTR_EXPIRE: |
| case ATTR_FLAG: |
| case ATTR_AGE: |
| w->data.val_i = -1; /* not used for NIS */ |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * Do not release nisbuf->domain. |
| * It's been set by yp_get_default_domain() |
| * and must not be freed. |
| * See man page yp_get_default_domain(3NSL) |
| * for details. |
| */ |
| if (nisbuf->master) |
| free(nisbuf->master); |
| if (nisbuf->scratch) |
| free(nisbuf->scratch); |
| if (nisbuf->c2scratch) |
| free(nisbuf->c2scratch); |
| free(nisbuf->pwd); |
| free(nisbuf); |
| |
| return (res); |
| } |
| |
| /* |
| * nis_getpwnam(name, items, rep) |
| * |
| * Get the account information of user 'name' |
| */ |
| /*ARGSUSED*/ |
| int |
| nis_getpwnam(char *name, attrlist *items, pwu_repository_t *rep, |
| void **buf) |
| { |
| nisbuf_t *nisbuf; |
| int nisresult; |
| |
| nisbuf = calloc(sizeof (*nisbuf), 1); |
| if (nisbuf == NULL) |
| return (PWU_NOMEM); |
| |
| nisbuf->pwd = malloc(sizeof (struct passwd)); |
| if (nisbuf->pwd == NULL) { |
| free(nisbuf); |
| return (PWU_NOMEM); |
| } |
| |
| /* |
| * Do not release nisbuf->domain. |
| * It is going to be set by yp_get_default_domain() |
| * and must not be freed. |
| * See man page yp_get_default_domain(3NSL) |
| * for details. |
| */ |
| if (yp_get_default_domain(&nisbuf->domain) != 0) { |
| syslog(LOG_ERR, "passwdutil.so: can't get domain"); |
| free(nisbuf->pwd); |
| free(nisbuf); |
| return (PWU_SERVER_ERROR); |
| } |
| |
| if (yp_master(nisbuf->domain, "passwd.byname", &nisbuf->master) != 0) { |
| syslog(LOG_ERR, |
| "passwdutil.so: can't get master for passwd map"); |
| if (nisbuf->master) |
| free(nisbuf->master); |
| free(nisbuf->pwd); |
| free(nisbuf); |
| return (PWU_SERVER_ERROR); |
| } |
| |
| nisresult = yp_match(nisbuf->domain, "passwd.byname", name, |
| strlen(name), &(nisbuf->scratch), |
| &(nisbuf->scratchlen)); |
| if (nisresult != 0) { |
| (void) free(nisbuf->pwd); |
| if (nisbuf->scratch) |
| (void) free(nisbuf->scratch); |
| if (nisbuf->master) |
| (void) free(nisbuf->master); |
| (void) free(nisbuf); |
| return (PWU_NOT_FOUND); |
| } |
| |
| nis_to_pwd(nisbuf->scratch, nisbuf->pwd); |
| |
| /* |
| * check for the C2 security flag "##" in the passwd field. |
| * If the first 2 chars in the passwd field is "##", get |
| * the user's passwd from passwd.adjunct.byname map. |
| * The lookup to this passwd.adjunct.byname map will only |
| * succeed if the caller's uid is 0 because only root user |
| * can use privilege port. |
| */ |
| if (nisbuf->pwd->pw_passwd[0] == '#' && |
| nisbuf->pwd->pw_passwd[1] == '#') { |
| char *key = &nisbuf->pwd->pw_passwd[2]; |
| int keylen; |
| char *p; |
| |
| keylen = strlen(key); |
| |
| nisresult = yp_match(nisbuf->domain, "passwd.adjunct.byname", |
| key, keylen, &(nisbuf->c2scratch), |
| &(nisbuf->c2scratchlen)); |
| |
| if (nisresult == 0 && nisbuf->c2scratch != NULL) { |
| /* Skip username (first field), and pick up password */ |
| p = nisbuf->c2scratch; |
| (void) strsep(&p, ":"); |
| nisbuf->pwd->pw_passwd = strsep(&p, ":"); |
| } |
| } |
| |
| *buf = (void *)nisbuf; |
| |
| return (PWU_SUCCESS); |
| } |
| |
| /* |
| * nis_update(items, rep, buf) |
| * |
| * update the information in "buf" with the attribute/values |
| * specified in "items". |
| */ |
| /*ARGSUSED*/ |
| int |
| nis_update(attrlist *items, pwu_repository_t *rep, void *buf) |
| { |
| attrlist *p; |
| nisbuf_t *nisbuf = (nisbuf_t *)buf; |
| char *salt; |
| |
| for (p = items; p != NULL; p = p->next) { |
| switch (p->type) { |
| case ATTR_NAME: |
| break; |
| /* |
| * Nothing special needs to be done for |
| * server policy |
| */ |
| case ATTR_PASSWD: |
| case ATTR_PASSWD_SERVER_POLICY: |
| salt = crypt_gensalt( |
| nisbuf->pwd->pw_passwd, nisbuf->pwd); |
| |
| if (salt == NULL) { |
| if (errno == ENOMEM) |
| return (PWU_NOMEM); |
| else { |
| /* algorithm problem? */ |
| syslog(LOG_AUTH | LOG_ALERT, |
| "passwdutil: crypt_gensalt " |
| "%m"); |
| return (PWU_UPDATE_FAILED); |
| } |
| } |
| nisbuf->pwd->pw_passwd = crypt(p->data.val_s, salt); |
| free(salt); |
| break; |
| case ATTR_UID: |
| nisbuf->pwd->pw_uid = (uid_t)p->data.val_i; |
| break; |
| case ATTR_GID: |
| nisbuf->pwd->pw_gid = (gid_t)p->data.val_i; |
| break; |
| case ATTR_AGE: |
| nisbuf->pwd->pw_age = p->data.val_s; |
| break; |
| case ATTR_COMMENT: |
| nisbuf->pwd->pw_comment = p->data.val_s; |
| break; |
| case ATTR_GECOS: |
| nisbuf->pwd->pw_gecos = p->data.val_s; |
| break; |
| case ATTR_HOMEDIR: |
| nisbuf->pwd->pw_dir = p->data.val_s; |
| break; |
| case ATTR_SHELL: |
| nisbuf->pwd->pw_shell = p->data.val_s; |
| break; |
| case ATTR_LSTCHG: |
| case ATTR_MIN: |
| case ATTR_MAX: |
| case ATTR_WARN: |
| case ATTR_INACT: |
| case ATTR_EXPIRE: |
| case ATTR_FLAG: |
| default: |
| break; |
| } |
| } |
| return (PWU_SUCCESS); |
| } |
| |
| /* |
| * nis_putpwnam(name, oldpw, rep, buf) |
| * |
| * Update the NIS server. The passwd structure in buf will be sent to |
| * the server for user "name" authenticating with password "oldpw". |
| */ |
| /*ARGSUSED*/ |
| int |
| nis_putpwnam(char *name, char *oldpw, pwu_repository_t *rep, |
| void *buf) |
| { |
| nisbuf_t *nisbuf = (nisbuf_t *)buf; |
| struct yppasswd yppasswd; |
| struct netconfig *nconf; |
| int ok; |
| enum clnt_stat ans; |
| CLIENT *client; |
| struct timeval timeout; |
| |
| if (strcmp(name, "root") == 0) |
| return (PWU_NOT_FOUND); |
| |
| yppasswd.oldpass = oldpw ? oldpw : ""; |
| yppasswd.newpw = *nisbuf->pwd; |
| |
| /* |
| * If we are privileged, we create a ticlts connection to the |
| * NIS server so that it can check our credentials |
| */ |
| if (nis_privileged(nisbuf)) { |
| nconf = getnetconfigent("ticlts"); |
| if (!nconf) { |
| syslog(LOG_ERR, |
| "passwdutil.so: Couldn't get netconfig entry"); |
| return (PWU_SYSTEM_ERROR); |
| } |
| client = clnt_tp_create(nisbuf->master, YPPASSWDPROG, |
| YPPASSWDVERS, nconf); |
| freenetconfigent(nconf); |
| } else { |
| /* Try IPv6 first */ |
| client = clnt_create(nisbuf->master, YPPASSWDPROG, |
| YPPASSWDVERS, "udp6"); |
| if (client == NULL) |
| client = clnt_create(nisbuf->master, YPPASSWDPROG, |
| YPPASSWDVERS, "udp"); |
| } |
| |
| if (client == NULL) { |
| syslog(LOG_ERR, |
| "passwdutil.so: couldn't create client to YP master"); |
| return (PWU_SERVER_ERROR); |
| } |
| |
| timeout.tv_usec = 0; |
| timeout.tv_sec = 55; /* ndp uses 55 seconds */ |
| |
| ans = CLNT_CALL(client, YPPASSWDPROC_UPDATE, xdr_yppasswd, |
| (char *)&yppasswd, xdr_int, (char *)&ok, timeout); |
| |
| if (nisbuf->pwd) |
| (void) free(nisbuf->pwd); |
| if (nisbuf->master) |
| (void) free(nisbuf->master); |
| if (nisbuf->scratch) |
| (void) free(nisbuf->scratch); |
| if (nisbuf->c2scratch) |
| (void) free(nisbuf->c2scratch); |
| |
| (void) clnt_destroy(client); |
| |
| if (ans != RPC_SUCCESS) { |
| return (PWU_UPDATE_FAILED); |
| } |
| |
| /* These errors are obtained from the yppasswdd.c code */ |
| switch (ok) { |
| case 2: return (PWU_DENIED); |
| case 8: return (PWU_BUSY); |
| case 9: return (PWU_SERVER_ERROR); |
| case 4: return (PWU_NOT_FOUND); |
| case 3: return (PWU_NO_CHANGE); |
| case 7: return (PWU_DENIED); |
| case 0: return (PWU_SUCCESS); |
| default: return (PWU_SYSTEM_ERROR); |
| } |
| } |