2 Unix SMB/CIFS implementation.
4 AIX loadable authentication module, providing identification and
5 authentication routines against Samba winbind/Windows NT Domain
7 Copyright (C) Tim Potter 2003
8 Copyright (C) Steve Roylance 2003
9 Copyright (C) Andrew Tridgell 2003-2004
11 This library is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Lesser General Public
13 License as published by the Free Software Foundation; either
14 version 3 of the License, or (at your option) any later version.
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Library General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public License
22 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 To install this module copy nsswitch/WINBIND to /usr/lib/security and add
28 "WINBIND" in /usr/lib/security/methods.cfg and /etc/security/user
30 Note that this module also provides authentication and password
31 changing routines, so you do not need to install the winbind PAM
35 http://publib16.boulder.ibm.com/doc_link/en_US/a_doc_lib/aixprggd/kernextc/sec_load_mod.htm
36 for some information in the interface that this module implements
38 Many thanks to Julianne Haugh for explaining some of the finer
39 details of this interface.
41 To debug this module use uess_test.c (which you can get from tridge)
42 or set "options=debug" in /usr/lib/security/methods.cfg
46 #include "winbind_client.h"
49 /* enable this to log which entry points have not been
51 #define LOG_UNIMPLEMENTED_CALLS 0
54 #define WB_AIX_ENCODED '_'
56 static int debug_enabled;
59 static void logit(const char *format, ...)
66 f = fopen("/tmp/WINBIND_DEBUG.log", "a");
69 vfprintf(f, format, ap);
75 #define HANDLE_ERRORS(ret) do { \
76 if ((ret) == NSS_STATUS_NOTFOUND) { \
79 } else if ((ret) != NSS_STATUS_SUCCESS) { \
85 #define STRCPY_RET(dest, src) \
87 if (strlen(src)+1 > sizeof(dest)) { errno = EINVAL; return -1; } \
91 #define STRCPY_RETNULL(dest, src) \
93 if (strlen(src)+1 > sizeof(dest)) { errno = EINVAL; return NULL; } \
98 /* free a passwd structure */
99 static void free_pwd(struct passwd *pwd)
102 free(pwd->pw_passwd);
109 /* free a group structure */
110 static void free_grp(struct group *grp)
115 free(grp->gr_passwd);
122 for (i=0; grp->gr_mem[i]; i++) {
123 free(grp->gr_mem[i]);
131 /* replace commas with nulls, and null terminate */
132 static void replace_commas(char *s)
135 for (p=strchr(s, ','); p; p = strchr(p+1, ',')) {
140 p0[strlen(p0)+1] = 0;
144 /* the decode_*() routines are used to cope with the fact that AIX 5.2
145 and below cannot handle user or group names longer than 8
146 characters in some interfaces. We use the normalize method to
147 provide a mapping to a username that fits, by using the form '_UID'
150 this only works if you can guarantee that the WB_AIX_ENCODED char
151 is not used as the first char of any other username
153 static unsigned decode_id(const char *name)
156 sscanf(name+1, "%u", &id);
160 static struct passwd *wb_aix_getpwuid(uid_t uid);
162 static char *decode_user(const char *name)
168 sscanf(name+1, "%u", &id);
169 pwd = wb_aix_getpwuid(id);
173 ret = strdup(pwd->pw_name);
177 logit("decoded '%s' -> '%s'\n", name, ret);
184 fill a struct passwd from a winbindd_pw struct, allocating as a single block
186 static struct passwd *fill_pwent(struct winbindd_pw *pw)
188 struct passwd *result;
190 result = calloc(1, sizeof(struct passwd));
196 result->pw_uid = pw->pw_uid;
197 result->pw_gid = pw->pw_gid;
198 result->pw_name = strdup(pw->pw_name);
199 result->pw_passwd = strdup(pw->pw_passwd);
200 result->pw_gecos = strdup(pw->pw_gecos);
201 result->pw_dir = strdup(pw->pw_dir);
202 result->pw_shell = strdup(pw->pw_shell);
209 fill a struct group from a winbindd_pw struct, allocating as a single block
211 static struct group *fill_grent(struct winbindd_gr *gr, char *gr_mem)
214 struct group *result;
217 result = calloc(1, sizeof(struct group));
223 result->gr_gid = gr->gr_gid;
225 result->gr_name = strdup(gr->gr_name);
226 result->gr_passwd = strdup(gr->gr_passwd);
228 /* Group membership */
229 if ((gr->num_gr_mem < 0) || !gr_mem) {
233 if (gr->num_gr_mem == 0) {
238 result->gr_mem = (char **)malloc(sizeof(char *) * (gr->num_gr_mem+1));
239 if (!result->gr_mem) {
240 free(result->gr_name);
241 free(result->gr_passwd);
247 /* Start looking at extra data */
249 for (name = strtok_r(gr_mem, ",", &p);
251 name = strtok_r(NULL, ",", &p)) {
252 if (i == gr->num_gr_mem) {
255 result->gr_mem[i] = strdup(name);
260 result->gr_mem[i] = NULL;
267 /* take a group id and return a filled struct group */
268 static struct group *wb_aix_getgrgid(gid_t gid)
270 struct winbindd_response response;
271 struct winbindd_request request;
275 logit("getgrgid %d\n", gid);
277 ZERO_STRUCT(response);
278 ZERO_STRUCT(request);
280 request.data.gid = gid;
282 ret = winbindd_request_response(NULL, WINBINDD_GETGRGID,
283 &request, &response);
285 logit("getgrgid ret=%d\n", ret);
289 grp = fill_grent(&response.data.gr, response.extra_data.data);
291 winbindd_free_response(&response);
296 /* take a group name and return a filled struct group */
297 static struct group *wb_aix_getgrnam(const char *name)
299 struct winbindd_response response;
300 struct winbindd_request request;
304 if (*name == WB_AIX_ENCODED) {
305 return wb_aix_getgrgid(decode_id(name));
308 logit("getgrnam '%s'\n", name);
310 ZERO_STRUCT(response);
311 ZERO_STRUCT(request);
313 STRCPY_RETNULL(request.data.groupname, name);
315 ret = winbindd_request_response(NULL, WINBINDD_GETGRNAM,
316 &request, &response);
320 grp = fill_grent(&response.data.gr, response.extra_data.data);
322 winbindd_free_response(&response);
328 /* this call doesn't have to fill in the gr_mem, but we do anyway
330 static struct group *wb_aix_getgracct(void *id, int type)
333 return wb_aix_getgrnam((char *)id);
336 return wb_aix_getgrgid(*(int *)id);
343 /* take a username and return a string containing a comma-separated
344 list of group id numbers to which the user belongs */
345 static char *wb_aix_getgrset(char *user)
347 struct winbindd_response response;
348 struct winbindd_request request;
356 if (*user == WB_AIX_ENCODED) {
357 r_user = decode_user(r_user);
364 logit("getgrset '%s'\n", r_user);
366 ZERO_STRUCT(response);
367 ZERO_STRUCT(request);
369 STRCPY_RETNULL(request.data.username, r_user);
371 if (*user == WB_AIX_ENCODED) {
375 ret = winbindd_request_response(NULL, WINBINDD_GETGROUPS,
376 &request, &response);
380 num_gids = response.data.num_entries;
381 gid_list = (gid_t *)response.extra_data.data;
383 /* allocate a space large enough to contruct the string */
384 tmpbuf = malloc(num_gids*12);
389 for (idx=i=0; i < num_gids-1; i++) {
390 idx += sprintf(tmpbuf+idx, "%u,", gid_list[i]);
392 idx += sprintf(tmpbuf+idx, "%u", gid_list[i]);
394 winbindd_free_response(&response);
400 /* take a uid and return a filled struct passwd */
401 static struct passwd *wb_aix_getpwuid(uid_t uid)
403 struct winbindd_response response;
404 struct winbindd_request request;
408 logit("getpwuid '%d'\n", uid);
410 ZERO_STRUCT(response);
411 ZERO_STRUCT(request);
413 request.data.uid = uid;
415 ret = winbindd_request_response(NULL, WINBINDD_GETPWUID,
416 &request, &response);
420 pwd = fill_pwent(&response.data.pw);
422 winbindd_free_response(&response);
424 logit("getpwuid gave ptr %p\n", pwd);
430 /* take a username and return a filled struct passwd */
431 static struct passwd *wb_aix_getpwnam(const char *name)
433 struct winbindd_response response;
434 struct winbindd_request request;
438 if (*name == WB_AIX_ENCODED) {
439 return wb_aix_getpwuid(decode_id(name));
442 logit("getpwnam '%s'\n", name);
444 ZERO_STRUCT(response);
445 ZERO_STRUCT(request);
447 STRCPY_RETNULL(request.data.username, name);
449 ret = winbindd_request_response(NULL, WINBINDD_GETPWNAM,
450 &request, &response);
454 pwd = fill_pwent(&response.data.pw);
456 winbindd_free_response(&response);
458 logit("getpwnam gave ptr %p\n", pwd);
466 static int wb_aix_lsuser(char *attributes[], attrval_t results[], int size)
469 struct winbindd_request request;
470 struct winbindd_response response;
474 if (size != 1 || strcmp(attributes[0], S_USERS) != 0) {
475 logit("invalid lsuser op\n");
480 ZERO_STRUCT(request);
481 ZERO_STRUCT(response);
483 ret = winbindd_request_response(NULL, WINBINDD_LIST_USERS,
484 &request, &response);
490 len = strlen(response.extra_data.data);
494 winbindd_free_response(&response);
499 memcpy(s, response.extra_data.data, len+1);
503 results[0].attr_un.au_char = s;
504 results[0].attr_flag = 0;
506 winbindd_free_response(&response);
515 static int wb_aix_lsgroup(char *attributes[], attrval_t results[], int size)
518 struct winbindd_request request;
519 struct winbindd_response response;
523 if (size != 1 || strcmp(attributes[0], S_GROUPS) != 0) {
524 logit("invalid lsgroup op\n");
529 ZERO_STRUCT(request);
530 ZERO_STRUCT(response);
532 ret = winbindd_request_response(NULL, WINBINDD_LIST_GROUPS,
533 &request, &response);
539 len = strlen(response.extra_data.data);
543 winbindd_free_response(&response);
548 memcpy(s, response.extra_data.data, len+1);
552 results[0].attr_un.au_char = s;
553 results[0].attr_flag = 0;
555 winbindd_free_response(&response);
561 static attrval_t pwd_to_group(struct passwd *pwd)
564 struct group *grp = wb_aix_getgrgid(pwd->pw_gid);
567 r.attr_flag = EINVAL;
570 r.attr_un.au_char = strdup(grp->gr_name);
577 static attrval_t pwd_to_groupsids(struct passwd *pwd)
582 if ( (s = wb_aix_getgrset(pwd->pw_name)) == NULL ) {
583 r.attr_flag = EINVAL;
587 if ( (p = malloc(strlen(s)+2)) == NULL ) {
588 r.attr_flag = ENOMEM;
596 r.attr_un.au_char = p;
601 static attrval_t pwd_to_sid(struct passwd *pwd)
603 struct winbindd_request request;
604 struct winbindd_response response;
607 ZERO_STRUCT(request);
608 ZERO_STRUCT(response);
610 request.data.uid = pwd->pw_uid;
614 * Removed because WINBINDD_UID_TO_SID is replaced by
615 * WINBINDD_XIDS_TO_SIDS. I don't have an AIX build
616 * environment around, so I did not convert this call. If
617 * someone stumbles over this, please contact me:
618 * vl@samba.org, I'll convert this.
620 if (winbindd_request_response(NULL, WINBINDD_UID_TO_SID,
621 &request, &response) !=
622 NSS_STATUS_SUCCESS) {
623 r.attr_flag = ENOENT;
626 r.attr_un.au_char = strdup(response.data.sid.sid);
629 r.attr_flag = ENOENT;
635 static int wb_aix_user_attrib(const char *key, char *attributes[],
636 attrval_t results[], int size)
641 pwd = wb_aix_getpwnam(key);
647 for (i=0;i<size;i++) {
648 results[i].attr_flag = 0;
650 if (strcmp(attributes[i], S_ID) == 0) {
651 results[i].attr_un.au_int = pwd->pw_uid;
652 #ifdef _AIXVERSION_530
653 } else if (strcmp(attributes[i], S_PGID) == 0) {
654 results[i].attr_un.au_int = pwd->pw_gid;
656 } else if (strcmp(attributes[i], S_PWD) == 0) {
657 results[i].attr_un.au_char = strdup(pwd->pw_passwd);
658 } else if (strcmp(attributes[i], S_HOME) == 0) {
659 results[i].attr_un.au_char = strdup(pwd->pw_dir);
660 } else if (strcmp(attributes[i], S_SHELL) == 0) {
661 results[i].attr_un.au_char = strdup(pwd->pw_shell);
662 } else if (strcmp(attributes[i], S_REGISTRY) == 0) {
663 results[i].attr_un.au_char = strdup("WINBIND");
664 } else if (strcmp(attributes[i], S_GECOS) == 0) {
665 results[i].attr_un.au_char = strdup(pwd->pw_gecos);
666 } else if (strcmp(attributes[i], S_PGRP) == 0) {
667 results[i] = pwd_to_group(pwd);
668 } else if (strcmp(attributes[i], S_GROUPS) == 0) {
669 results[i] = pwd_to_groupsids(pwd);
670 } else if (strcmp(attributes[i], "SID") == 0) {
671 results[i] = pwd_to_sid(pwd);
673 logit("Unknown user attribute '%s'\n", attributes[i]);
674 results[i].attr_flag = EINVAL;
683 static int wb_aix_group_attrib(const char *key, char *attributes[],
684 attrval_t results[], int size)
689 grp = wb_aix_getgrnam(key);
695 for (i=0;i<size;i++) {
696 results[i].attr_flag = 0;
698 if (strcmp(attributes[i], S_PWD) == 0) {
699 results[i].attr_un.au_char = strdup(grp->gr_passwd);
700 } else if (strcmp(attributes[i], S_ID) == 0) {
701 results[i].attr_un.au_int = grp->gr_gid;
703 logit("Unknown group attribute '%s'\n", attributes[i]);
704 results[i].attr_flag = EINVAL;
715 called for user/group enumerations
717 static int wb_aix_getentry(char *key, char *table, char *attributes[],
718 attrval_t results[], int size)
720 logit("Got getentry with key='%s' table='%s' size=%d attributes[0]='%s'\n",
721 key, table, size, attributes[0]);
723 if (strcmp(key, "ALL") == 0 &&
724 strcmp(table, "user") == 0) {
725 return wb_aix_lsuser(attributes, results, size);
728 if (strcmp(key, "ALL") == 0 &&
729 strcmp(table, "group") == 0) {
730 return wb_aix_lsgroup(attributes, results, size);
733 if (strcmp(table, "user") == 0) {
734 return wb_aix_user_attrib(key, attributes, results, size);
737 if (strcmp(table, "group") == 0) {
738 return wb_aix_group_attrib(key, attributes, results, size);
741 logit("Unknown getentry operation key='%s' table='%s'\n", key, table);
750 called to start the backend
752 static void *wb_aix_open(const char *name, const char *domain, int mode, char *options)
754 if (strstr(options, "debug")) {
757 logit("open name='%s' mode=%d domain='%s' options='%s'\n", name, domain,
762 static void wb_aix_close(void *token)
768 #ifdef HAVE_STRUCT_SECMETHOD_TABLE_METHOD_ATTRLIST
770 return a list of additional attributes supported by the backend
772 static attrlist_t **wb_aix_attrlist(void)
774 /* pretty confusing but we are allocating the array of pointers
775 and the structures we'll be pointing to all at once. So
776 you need N+1 pointers and N structures. */
778 attrlist_t **ret = NULL;
779 attrlist_t *offset = NULL;
789 /* user attributes */
790 {S_ID, AL_USERATTR, SEC_INT},
791 {S_PGRP, AL_USERATTR, SEC_CHAR},
792 {S_HOME, AL_USERATTR, SEC_CHAR},
793 {S_SHELL, AL_USERATTR, SEC_CHAR},
794 #ifdef _AIXVERSION_530
795 {S_PGID, AL_USERATTR, SEC_INT},
797 {S_GECOS, AL_USERATTR, SEC_CHAR},
798 {S_SHELL, AL_USERATTR, SEC_CHAR},
799 {S_PGRP, AL_USERATTR, SEC_CHAR},
800 {S_GROUPS, AL_USERATTR, SEC_LIST},
801 {"SID", AL_USERATTR, SEC_CHAR},
803 /* group attributes */
804 {S_ID, AL_GROUPATTR, SEC_INT}
807 logit("method attrlist called\n");
809 n = sizeof(attr_list) / sizeof(struct attr_types);
810 size = (n*sizeof(attrlist_t *));
812 if ( (ret = malloc( size )) == NULL ) {
817 /* offset to where the structures start in the buffer */
819 offset = (attrlist_t *)(ret + n);
821 /* now loop over the user_attr_list[] array and add
824 for ( i=0; i<n; i++ ) {
825 attrlist_t *a = malloc(sizeof(attrlist_t));
828 /* this is bad. Just bail */
832 a->al_name = strdup(attr_list[i].name);
833 a->al_flags = attr_list[i].flags;
834 a->al_type = attr_list[i].type;
846 turn a long username into a short one. Needed to cope with the 8 char
847 username limit in AIX 5.2 and below
849 static int wb_aix_normalize(char *longname, char *shortname)
853 logit("normalize '%s'\n", longname);
855 /* automatically cope with AIX 5.3 with longer usernames
857 if (S_NAMELEN > strlen(longname)) {
858 strcpy(shortname, longname);
862 pwd = wb_aix_getpwnam(longname);
868 sprintf(shortname, "%c%07u", WB_AIX_ENCODED, pwd->pw_uid);
879 static int wb_aix_authenticate(char *user, char *pass,
880 int *reenter, char **message)
882 struct winbindd_request request;
883 struct winbindd_response response;
887 logit("authenticate '%s' response='%s'\n", user, pass);
892 /* Send off request */
893 ZERO_STRUCT(request);
894 ZERO_STRUCT(response);
896 if (*user == WB_AIX_ENCODED) {
897 r_user = decode_user(r_user);
899 return AUTH_NOTFOUND;
903 STRCPY_RET(request.data.auth.user, r_user);
904 STRCPY_RET(request.data.auth.pass, pass);
906 if (*user == WB_AIX_ENCODED) {
910 result = winbindd_request_response(NULL, WINBINDD_PAM_AUTH,
911 &request, &response);
913 winbindd_free_response(&response);
915 logit("auth result %d for '%s'\n", result, user);
917 if (result == NSS_STATUS_SUCCESS) {
927 change a user password
929 static int wb_aix_chpass(char *user, char *oldpass, char *newpass, char **message)
931 struct winbindd_request request;
932 struct winbindd_response response;
936 if (*user == WB_AIX_ENCODED) {
937 r_user = decode_user(r_user);
944 logit("chpass '%s' old='%s' new='%s'\n", r_user, oldpass, newpass);
948 /* Send off request */
949 ZERO_STRUCT(request);
950 ZERO_STRUCT(response);
952 STRCPY_RET(request.data.chauthtok.user, r_user);
953 STRCPY_RET(request.data.chauthtok.oldpass, oldpass);
954 STRCPY_RET(request.data.chauthtok.newpass, newpass);
956 if (*user == WB_AIX_ENCODED) {
960 result = winbindd_request_response(NULL, WINBINDD_PAM_CHAUTHTOK,
961 &request, &response);
963 winbindd_free_response(&response);
965 if (result == NSS_STATUS_SUCCESS) {
975 don't do any password strength testing for now
977 static int wb_aix_passwdrestrictions(char *user, char *newpass, char *oldpass,
980 logit("passwdresrictions called for '%s'\n", user);
985 static int wb_aix_passwdexpired(char *user, char **message)
987 logit("passwdexpired '%s'\n", user);
988 /* we should check the account bits here */
994 we can't return a crypt() password
996 static char *wb_aix_getpasswd(char *user)
998 logit("getpasswd '%s'\n", user);
1004 this is called to update things like the last login time. We don't
1005 currently pass this onto the DC
1007 static int wb_aix_putentry(char *key, char *table, char *attributes[],
1008 attrval_t values[], int size)
1010 logit("putentry key='%s' table='%s' attrib='%s'\n",
1011 key, table, size>=1?attributes[0]:"<null>");
1016 static int wb_aix_commit(char *key, char *table)
1018 logit("commit key='%s' table='%s'\n");
1023 static int wb_aix_getgrusers(char *group, void *result, int type, int *size)
1025 logit("getgrusers group='%s'\n", group);
1031 #define DECL_METHOD(x) \
1032 int method_ ## x(void) \
1034 logit("UNIMPLEMENTED METHOD '%s'\n", #x); \
1039 #if LOG_UNIMPLEMENTED_CALLS
1040 DECL_METHOD(delgroup);
1041 DECL_METHOD(deluser);
1042 DECL_METHOD(newgroup);
1043 DECL_METHOD(newuser);
1044 DECL_METHOD(putgrent);
1045 DECL_METHOD(putgrusers);
1046 DECL_METHOD(putpwent);
1048 DECL_METHOD(unlock);
1049 DECL_METHOD(getcred);
1050 DECL_METHOD(setcred);
1051 DECL_METHOD(deletecred);
1054 int wb_aix_init(struct secmethod_table *methods)
1056 ZERO_STRUCTP(methods);
1058 #ifdef HAVE_STRUCT_SECMETHOD_TABLE_METHOD_VERSION
1059 methods->method_version = SECMETHOD_VERSION_520;
1062 methods->method_getgrgid = wb_aix_getgrgid;
1063 methods->method_getgrnam = wb_aix_getgrnam;
1064 methods->method_getgrset = wb_aix_getgrset;
1065 methods->method_getpwnam = wb_aix_getpwnam;
1066 methods->method_getpwuid = wb_aix_getpwuid;
1067 methods->method_getentry = wb_aix_getentry;
1068 methods->method_open = wb_aix_open;
1069 methods->method_close = wb_aix_close;
1070 methods->method_normalize = wb_aix_normalize;
1071 methods->method_passwdexpired = wb_aix_passwdexpired;
1072 methods->method_putentry = wb_aix_putentry;
1073 methods->method_getpasswd = wb_aix_getpasswd;
1074 methods->method_authenticate = wb_aix_authenticate;
1075 methods->method_commit = wb_aix_commit;
1076 methods->method_chpass = wb_aix_chpass;
1077 methods->method_passwdrestrictions = wb_aix_passwdrestrictions;
1078 methods->method_getgracct = wb_aix_getgracct;
1079 methods->method_getgrusers = wb_aix_getgrusers;
1080 #ifdef HAVE_STRUCT_SECMETHOD_TABLE_METHOD_ATTRLIST
1081 methods->method_attrlist = wb_aix_attrlist;
1084 #if LOG_UNIMPLEMENTED_CALLS
1085 methods->method_delgroup = method_delgroup;
1086 methods->method_deluser = method_deluser;
1087 methods->method_newgroup = method_newgroup;
1088 methods->method_newuser = method_newuser;
1089 methods->method_putgrent = method_putgrent;
1090 methods->method_putgrusers = method_putgrusers;
1091 methods->method_putpwent = method_putpwent;
1092 methods->method_lock = method_lock;
1093 methods->method_unlock = method_unlock;
1094 methods->method_getcred = method_getcred;
1095 methods->method_setcred = method_setcred;
1096 methods->method_deletecred = method_deletecred;
1099 return AUTH_SUCCESS;