diff options
Diffstat (limited to 'usr/src/lib')
207 files changed, 63402 insertions, 2069 deletions
diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile index 68613f9412..ab96c6624e 100644 --- a/usr/src/lib/Makefile +++ b/usr/src/lib/Makefile @@ -234,6 +234,7 @@ SUBDIRS += \ libidmap \ libipmi \ libexacct/demo \ + smbsrv \ $($(MACH)_SUBDIRS) sparc_SUBDIRS= .WAIT \ @@ -421,6 +422,7 @@ HDRSUBDIRS= \ libkrb5 \ libshare \ libidmap \ + smbsrv \ $($(MACH)_HDRSUBDIRS) $(CLOSED_BUILD)HDRSUBDIRS += \ @@ -506,7 +508,7 @@ libinetcfg: libnsl libsocket libdevinfo libkmf: libcryptoutil pkcs11 openssl libnsl: libmd5 libscf libmapid: libresolv -libuuid: libdlpi libdladm +libuuid: libdlpi libdladm libinetutil: libsocket libsecdb: libnsl libsasl: libgss libsocket pkcs11 libmd @@ -528,7 +530,7 @@ libwrap: libnsl libsocket libwanboot: libnvpair libresolv libnsl libsocket libdevinfo libinetutil \ libdhcputil openssl libwanbootutil: libnsl -pam_modules: libproject passwdutil $(SMARTCARD) +pam_modules: libproject passwdutil $(SMARTCARD) smbsrv libscf: libuutil libmd libgen libinetsvc: libscf librestart: libuutil libscf @@ -545,6 +547,8 @@ brand: libc libsocket libshare: libscf libzfs libuuid libfsmgt libsecdb libexacct/demo: libexacct libproject libsocket libnsl libtsalarm: libpcp +smbsrv: libsocket libnsl libmd libxnet libpthread librt \ + libshare libidmap pkcs11 # # The reason this rule checks for the existence of the diff --git a/usr/src/lib/common/inc/c_synonyms.h b/usr/src/lib/common/inc/c_synonyms.h index 50bb091a58..8b8a610531 100644 --- a/usr/src/lib/common/inc/c_synonyms.h +++ b/usr/src/lib/common/inc/c_synonyms.h @@ -744,6 +744,7 @@ extern "C" { #define readv _readv #define realpath _realpath #define remque _remque +#define renameat _renameat #define resolvepath _resolvepath #define rmdir _rmdir #define rwlock_destroy _rwlock_destroy diff --git a/usr/src/lib/libbc/inc/include/tzfile.h b/usr/src/lib/libbc/inc/include/tzfile.h index d6e43f03c2..b094236256 100644 --- a/usr/src/lib/libbc/inc/include/tzfile.h +++ b/usr/src/lib/libbc/inc/include/tzfile.h @@ -2,9 +2,8 @@ * CDDL HEADER START * * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License"). You may not use this file except in compliance - * with the License. + * 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. @@ -20,139 +19,17 @@ * CDDL HEADER END */ /* - * Copyright 1989 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* from Arthur Olson's 6.1 */ -#pragma ident "%Z%%M% %I% %E% SMI" - -#ifndef TZFILE_H - -#define TZFILE_H - -/* -** Information about time zone files. -*/ - -#define TZDIR "/usr/share/lib/zoneinfo" /* Time zone object file directory */ - -#define TZDEFAULT (getenv("TZ")) - -#define TZDEFRULES "posixrules" - -/* -** Each file begins with. . . -*/ - -struct tzhead { - char tzh_reserved[24]; /* reserved for future use */ - char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ - char tzh_leapcnt[4]; /* coded number of leap seconds */ - char tzh_timecnt[4]; /* coded number of transition times */ - char tzh_typecnt[4]; /* coded number of local time types */ - char tzh_charcnt[4]; /* coded number of abbr. chars */ -}; - -/* -** . . .followed by. . . -** -** tzh_timecnt (char [4])s coded transition times a la time(2) -** tzh_timecnt (unsigned char)s types of local time starting at above -** tzh_typecnt repetitions of -** one (char [4]) coded GMT offset in seconds -** one (unsigned char) used to set tm_isdst -** one (unsigned char) that's an abbreviation list index -** tzh_charcnt (char)s '\0'-terminated zone abbreviations -** tzh_leapcnt repetitions of -** one (char [4]) coded leap second transition times -** one (char [4]) total correction after above -** tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition -** time is standard time, if FALSE, -** transition time is wall clock time -** if absent, transition times are -** assumed to be wall clock time -*/ - -/* -** In the current implementation, "tzset()" refuses to deal with files that -** exceed any of the limits below. -*/ +#ifndef _TZFILE_H +#define _TZFILE_H -/* -** The TZ_MAX_TIMES value below is enough to handle a bit more than a -** year's worth of solar time (corrected daily to the nearest second) or -** 138 years of Pacific Presidential Election time -** (where there are three time zone transitions every fourth year). -*/ -#define TZ_MAX_TIMES 370 - -#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ - -#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ - -#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ - -#define SECSPERMIN 60 -#define MINSPERHOUR 60 -#define HOURSPERDAY 24 -#define DAYSPERWEEK 7 -#define DAYSPERNYEAR 365 -#define DAYSPERLYEAR 366 -#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) -#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) -#define MONSPERYEAR 12 - -#define TM_SUNDAY 0 -#define TM_MONDAY 1 -#define TM_TUESDAY 2 -#define TM_WEDNESDAY 3 -#define TM_THURSDAY 4 -#define TM_FRIDAY 5 -#define TM_SATURDAY 6 - -#define TM_JANUARY 0 -#define TM_FEBRUARY 1 -#define TM_MARCH 2 -#define TM_APRIL 3 -#define TM_MAY 4 -#define TM_JUNE 5 -#define TM_JULY 6 -#define TM_AUGUST 7 -#define TM_SEPTEMBER 8 -#define TM_OCTOBER 9 -#define TM_NOVEMBER 10 -#define TM_DECEMBER 11 - -#define TM_YEAR_BASE 1900 - -#define EPOCH_YEAR 1970 -#define EPOCH_WDAY TM_THURSDAY - -/* -** Accurate only for the past couple of centuries; -** that will probably do. -*/ - -#define isleap(y) (((y) % 4) == 0 && ((y) % 100) != 0 || ((y) % 400) == 0) - -/* -** Use of the underscored variants may cause problems if you move your code to -** certain System-V-based systems; for maximum portability, use the -** underscore-free variants. The underscored variants are provided for -** backward compatibility only; they may disappear from future versions of -** this file. -*/ +#pragma ident "%Z%%M% %I% %E% SMI" -#define SECS_PER_MIN SECSPERMIN -#define MINS_PER_HOUR MINSPERHOUR -#define HOURS_PER_DAY HOURSPERDAY -#define DAYS_PER_WEEK DAYSPERWEEK -#define DAYS_PER_NYEAR DAYSPERNYEAR -#define DAYS_PER_LYEAR DAYSPERLYEAR -#define SECS_PER_HOUR SECSPERHOUR -#define SECS_PER_DAY SECSPERDAY -#define MONS_PER_YEAR MONSPERYEAR +#include <sys/tzfile.h> -#endif /* !defined TZFILE_H */ +#endif /* _TZFILE_H */ diff --git a/usr/src/lib/libbsm/audit_event.txt b/usr/src/lib/libbsm/audit_event.txt index 8192080da3..125ef50174 100644 --- a/usr/src/lib/libbsm/audit_event.txt +++ b/usr/src/lib/libbsm/audit_event.txt @@ -444,6 +444,8 @@ 6241:AUE_uadmin_remount:uadmin(1m) - remount:ss 6242:AUE_uadmin_ftrace:uadmin(1m) - ftrace:ss 6243:AUE_uadmin_swapctl:uadmin(1m) - swapctl:ss +6237:AUE_smbd_session:smbd(1m) session setup:lo +6238:AUE_smbd_logoff:smbd(1m) session logoff:lo # # Trusted Extensions events: # diff --git a/usr/src/lib/libbsm/common/adt.xml b/usr/src/lib/libbsm/common/adt.xml index 3ae2957188..a91361e402 100644 --- a/usr/src/lib/libbsm/common/adt.xml +++ b/usr/src/lib/libbsm/common/adt.xml @@ -1231,8 +1231,61 @@ Use is subject to license terms. <see>uadmin(1M)</see> </event> +<!-- smbd service event; smbd session setup --> + <event id="AUE_smbd_session" header="0" idNo="58" omit="JNI"> + <title>smbd</title> + <program>/usr/lib/smbsrv/smbd</program> + <entry id="subject"> + <internal token="subject"/> + <external opt="none"/> + </entry> + <entry id="domain"> + <internal token="text"/> + <external opt="required" type="char*"/> + <comment>domain</comment> + </entry> + <entry id="username"> + <internal token="text"/> + <external opt="required" type="char*"/> + <comment>username</comment> + </entry> + <entry id="sid"> + <internal token="text"/> + <external opt="optional" type="char*"/> + <comment>sid</comment> + </entry> + <entry id="return"> + <internal token="return"/> + <external opt="none"/> + </entry> + </event> + +<!-- smbd service event; smbd session logoff --> + <event id="AUE_smbd_logoff" header="0" idNo="59" omit="JNI"> + <title>smbd</title> + <program>/usr/lib/smbsrv/smbd</program> + <entry id="subject"> + <internal token="subject"/> + <external opt="none"/> + </entry> + <entry id="domain"> + <internal token="text"/> + <external opt="required" type="char*"/> + <comment>domain</comment> + </entry> + <entry id="username"> + <internal token="text"/> + <external opt="required" type="char*"/> + <comment>username</comment> + </entry> + <entry id="return"> + <internal token="return"/> + <external opt="none"/> + </entry> + </event> + <!-- add new events here with the next higher idNo --> -<!-- Highest idNo is 57, so next is 58, then fix this comment --> +<!-- Highest idNo is 59, so next is 60, then fix this comment --> <!-- end of C Only events --> diff --git a/usr/src/lib/libc/Makefile.targ b/usr/src/lib/libc/Makefile.targ index b8237246a4..95a0a3b0e3 100644 --- a/usr/src/lib/libc/Makefile.targ +++ b/usr/src/lib/libc/Makefile.targ @@ -272,6 +272,10 @@ $(COMOBJS:%=pics/%): $(SRC)/common/util/$$(@F:.o=.c) $(COMPILE.c) -o $@ $(SRC)/common/util/$(@F:.o=.c) $(POST_PROCESS_O) +$(XATTROBJS:%=pics/%): $(SRC)/common/xattr/$$(@F:.o=.c) + $(COMPILE.c) -o $@ $(SRC)/common/xattr/$(@F:.o=.c) + $(POST_PROCESS_O) + $(DTRACEOBJS:%=pics/%): $(SRC)/common/dtrace/$$(@F:.o=.c) $(COMPILE.c) -o $@ $(SRC)/common/dtrace/$(@F:.o=.c) $(POST_PROCESS_O) diff --git a/usr/src/lib/libc/amd64/Makefile b/usr/src/lib/libc/amd64/Makefile index 0e2d924805..0539645b34 100644 --- a/usr/src/lib/libc/amd64/Makefile +++ b/usr/src/lib/libc/amd64/Makefile @@ -85,6 +85,8 @@ FPASMOBJS= \ ATOMICOBJS= \ atomic.o +XATTROBJS= \ + xattr_common.o COMOBJS= \ bcmp.o \ bcopy.o \ @@ -99,6 +101,7 @@ GENOBJS= \ _getsp.o \ abs.o \ alloca.o \ + attrat.o \ byteorder.o \ cache.o \ cuexit.o \ @@ -849,6 +852,7 @@ MOSTOBJS= \ $(I386FPOBJS) \ $(FPASMOBJS) \ $(ATOMICOBJS) \ + $(XATTROBJS) \ $(COMOBJS) \ $(GENOBJS) \ $(PORTFP) \ @@ -966,6 +970,7 @@ CLOBBERFILES += $(LIB_PIC) # list of C source for lint SRCS= \ $(ATOMICOBJS:%.o=$(SRC)/common/atomic/%.c) \ + $(XATTROBJS:%.o=$(SRC)/common/xattr/%.c) \ $(COMOBJS:%.o=$(SRC)/common/util/%.c) \ $(PORTFP:%.o=../port/fp/%.c) \ $(PORTGEN:%.o=../port/gen/%.c) \ diff --git a/usr/src/lib/libc/i386/Makefile.com b/usr/src/lib/libc/i386/Makefile.com index 6fe1a1c5cc..5fb4ffea84 100644 --- a/usr/src/lib/libc/i386/Makefile.com +++ b/usr/src/lib/libc/i386/Makefile.com @@ -83,6 +83,9 @@ FPASMOBJS= \ ATOMICOBJS= \ atomic.o +XATTROBJS= \ + xattr_common.o + COMOBJS= \ bcmp.o \ bcopy.o \ @@ -389,6 +392,7 @@ PORTGEN= \ atoi.o \ atol.o \ atoll.o \ + attrat.o \ attropen.o \ atexit.o \ atfork.o \ @@ -884,6 +888,7 @@ MOSTOBJS= \ $(FPOBJS) \ $(FPASMOBJS) \ $(ATOMICOBJS) \ + $(XATTROBJS) \ $(COMOBJS) \ $(DTRACEOBJS) \ $(GENOBJS) \ @@ -1026,6 +1031,7 @@ CLOBBERFILES += $(LIB_PIC) # list of C source for lint SRCS= \ $(ATOMICOBJS:%.o=$(SRC)/common/atomic/%.c) \ + $(XATTROBJS:%.o=$(SRC)/common/xattr/%.c) \ $(COMOBJS:%.o=$(SRC)/common/util/%.c) \ $(DTRACEOBJS:%.o=$(SRC)/common/dtrace/%.c) \ $(PORTFP:%.o=../port/fp/%.c) \ diff --git a/usr/src/lib/libc/port/gen/attrat.c b/usr/src/lib/libc/port/gen/attrat.c new file mode 100644 index 0000000000..f2a26d17cd --- /dev/null +++ b/usr/src/lib/libc/port/gen/attrat.c @@ -0,0 +1,273 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include "synonyms.h" +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <mtlib.h> +#include <attr.h> +#include <sys/types.h> +#include <sys/syscall.h> +#include <sys/stat.h> +#include <sys/filio.h> +#include <unistd.h> +#include <dlfcn.h> +#include <stdio.h> + +static int (*nvpacker)(nvlist_t *, char **, size_t *, int, int); +static int (*nvsize)(nvlist_t *, size_t *, int); +static int (*nvunpacker)(char *, size_t, nvlist_t **); +static mutex_t attrlock = DEFAULTMUTEX; +static int initialized; +extern int __openattrdirat(int basefd, const char *name); + +static char *xattr_view_name[XATTR_VIEW_LAST] = { + VIEW_READONLY, + VIEW_READWRITE +}; + +static int +attrat_init() +{ + if (initialized == 0) { + lmutex_lock(&attrlock); + if (initialized == 1) { + lmutex_unlock(&attrlock); + return (0); + } + + void *libnvhandle = dlopen("libnvpair.so.1", RTLD_LAZY); + if (libnvhandle == NULL || (nvpacker = (int (*)(nvlist_t *, + char **, size_t *, int, int)) dlsym(libnvhandle, + "nvlist_pack")) == NULL || + (nvsize = (int (*)(nvlist_t *, + size_t *, int)) dlsym(libnvhandle, + "nvlist_size")) == NULL || + (nvunpacker = (int (*)(char *, size_t, + nvlist_t **)) dlsym(libnvhandle, + "nvlist_unpack")) == NULL) { + if (libnvhandle) + dlclose(libnvhandle); + lmutex_unlock(&attrlock); + return (EINVAL); + } + + initialized = 1; + lmutex_unlock(&attrlock); + } + return (0); +} + +static int +attr_nv_pack(nvlist_t *request, void **nv_request, size_t *nv_requestlen) +{ + size_t bufsize; + char *packbuf = NULL; + + if (nvsize(request, &bufsize, NV_ENCODE_XDR) != 0) { + return (EINVAL); + } + + packbuf = malloc(bufsize); + if (packbuf == NULL) + return (EINVAL); + if (nvpacker(request, &packbuf, &bufsize, NV_ENCODE_XDR, 0) != 0) { + free(packbuf); + } else { + *nv_request = (void *)packbuf; + *nv_requestlen = bufsize; + } + return (0); +} + +static const char * +view_to_name(xattr_view_t view) +{ + if (view >= XATTR_VIEW_LAST || view < 0) + return (NULL); + return (xattr_view_name[view]); +} + +static int +xattr_openat(int basefd, xattr_view_t view, int mode) +{ + const char *xattrname; + int xattrfd; + int oflag; + + switch (view) { + case XATTR_VIEW_READONLY: + oflag = O_RDONLY; + break; + case XATTR_VIEW_READWRITE: + oflag = mode & O_RDWR; + break; + default: + (void) __set_errno(EINVAL); + return (-1); + } + if (mode & O_XATTR) + oflag |= O_XATTR; + + xattrname = view_to_name(view); + xattrfd = openat(basefd, xattrname, oflag); + if (xattrfd < 0) + return (xattrfd); + /* Don't cache sysattr info (advisory) */ + (void) directio(xattrfd, DIRECTIO_ON); + return (xattrfd); +} + +static int +cgetattr(int fd, nvlist_t **response) +{ + int error; + int bytesread; + void *nv_response; + size_t nv_responselen; + struct stat buf; + + if (error = attrat_init()) + return (__set_errno(error)); + if ((error = fstat(fd, &buf)) != 0) + return (__set_errno(error)); + nv_responselen = buf.st_size; + + if ((nv_response = malloc(nv_responselen)) == NULL) + return (__set_errno(ENOMEM)); + bytesread = read(fd, nv_response, nv_responselen); + if (bytesread != nv_responselen) + return (__set_errno(EFAULT)); + + error = nvunpacker(nv_response, nv_responselen, response); + free(nv_response); + return (error); +} + +static int +csetattr(int fd, nvlist_t *request) +{ + int error, saveerrno; + int byteswritten; + void *nv_request; + size_t nv_requestlen; + + if (error = attrat_init()) + return (__set_errno(error)); + + if ((error = attr_nv_pack(request, &nv_request, &nv_requestlen)) != 0) + return (__set_errno(error)); + + (void) __set_errno(0); + byteswritten = write(fd, nv_request, nv_requestlen); + if (byteswritten != nv_requestlen) { + saveerrno = errno; + free(nv_request); + errno = saveerrno; + return (__set_errno(errno)); + } + + free(nv_request); + return (0); +} + +int +fgetattr(int basefd, xattr_view_t view, nvlist_t **response) +{ + int error, saveerrno, xattrfd; + + if ((xattrfd = xattr_openat(basefd, view, O_XATTR)) < 0) + return (xattrfd); + + error = cgetattr(xattrfd, response); + saveerrno = errno; + (void) close(xattrfd); + errno = saveerrno; + return (error); +} + +int +fsetattr(int basefd, xattr_view_t view, nvlist_t *request) +{ + int error, saveerrno, xattrfd; + + if ((xattrfd = xattr_openat(basefd, view, O_RDWR | O_XATTR)) < 0) + return (xattrfd); + error = csetattr(xattrfd, request); + saveerrno = errno; + (void) close(xattrfd); + errno = saveerrno; + return (error); +} + +int +getattrat(int basefd, xattr_view_t view, const char *name, nvlist_t **response) +{ + int error, saveerrno, namefd, xattrfd; + + if ((namefd = __openattrdirat(basefd, name)) < 0) + return (namefd); + + if ((xattrfd = xattr_openat(namefd, view, 0)) < 0) { + saveerrno = errno; + (void) close(namefd); + errno = saveerrno; + return (xattrfd); + } + + error = cgetattr(xattrfd, response); + saveerrno = errno; + (void) close(namefd); + (void) close(xattrfd); + errno = saveerrno; + return (error); +} + +int +setattrat(int basefd, xattr_view_t view, const char *name, nvlist_t *request) +{ + int error, saveerrno, namefd, xattrfd; + + if ((namefd = __openattrdirat(basefd, name)) < 0) + return (namefd); + + if ((xattrfd = xattr_openat(namefd, view, O_RDWR)) < 0) { + saveerrno = errno; + (void) close(namefd); + errno = saveerrno; + return (xattrfd); + } + + error = csetattr(xattrfd, request); + saveerrno = errno; + (void) close(namefd); + (void) close(xattrfd); + errno = saveerrno; + return (error); +} diff --git a/usr/src/lib/libc/port/gen/privlib.c b/usr/src/lib/libc/port/gen/privlib.c index 1968f7eaa4..57218876bc 100644 --- a/usr/src/lib/libc/port/gen/privlib.c +++ b/usr/src/lib/libc/port/gen/privlib.c @@ -442,7 +442,7 @@ priv_set(priv_op_t op, priv_ptype_t setname, ...) /* * priv_ineffect(privilege). - * tests the existance of a privilege against the effective set. + * tests the existence of a privilege against the effective set. */ boolean_t priv_ineffect(const char *priv) diff --git a/usr/src/lib/libc/port/llib-lc b/usr/src/lib/libc/port/llib-lc index 3e3bba59ca..e74313a424 100644 --- a/usr/src/lib/libc/port/llib-lc +++ b/usr/src/lib/libc/port/llib-lc @@ -31,6 +31,7 @@ #include <aio.h> #include <alloca.h> +#include <attr.h> #include <atomic.h> #include <ctype.h> #include <deflt.h> diff --git a/usr/src/lib/libc/port/mapfile-vers b/usr/src/lib/libc/port/mapfile-vers index 3e658f68e6..717c4d9186 100644 --- a/usr/src/lib/libc/port/mapfile-vers +++ b/usr/src/lib/libc/port/mapfile-vers @@ -64,8 +64,11 @@ SUNW_1.23 { # SunOS 5.11 (Solaris 11) err; errx; fdatasync; + fgetattr; forkallx; forkx; + fsetattr; + getattrat; htonl; htons; lio_listio; @@ -111,6 +114,7 @@ SUNW_1.23 { # SunOS 5.11 (Solaris 11) sem_trywait; sem_unlink; sem_wait; + setattrat; _sharefs; shm_open; shm_unlink; @@ -1408,6 +1412,11 @@ SUNWprivate_1.1 { _atomic_swap_uint = NODYNSORT; _atomic_swap_ulong = NODYNSORT; _atomic_swap_ushort = NODYNSORT; + attr_count; + attr_to_data_type; + attr_to_name; + attr_to_option; + attr_to_xattr_view; _autofssys; _brk; __btowc_dense; @@ -1762,6 +1771,7 @@ SUNWprivate_1.1 { __mutex_trylock; _mutex_unlock = NODYNSORT; __mutex_unlock; + name_to_attr; _nanosleep; __nan_read; __nan_written; @@ -1807,6 +1817,8 @@ SUNWprivate_1.1 { __nsw_getconfig_v1; __nthreads; __numeric_init; + __openattrdirat; + option_to_attr; _openlog; _plock; _port_alert; diff --git a/usr/src/lib/libc/port/sys/fsmisc.c b/usr/src/lib/libc/port/sys/fsmisc.c index 88415c5363..491cc73732 100644 --- a/usr/src/lib/libc/port/sys/fsmisc.c +++ b/usr/src/lib/libc/port/sys/fsmisc.c @@ -2,9 +2,8 @@ * CDDL HEADER START * * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License"). You may not use this file except in compliance - * with the License. + * 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. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -60,3 +59,9 @@ renameat(int fromfd, const char *fromname, int tofd, const char *toname) { return (syscall(SYS_fsat, 7, fromfd, fromname, tofd, toname)); } + +int +__openattrdirat(int fd, const char *name) +{ + return (syscall(SYS_fsat, 9, fd, name)); +} diff --git a/usr/src/lib/libc/sparc/Makefile b/usr/src/lib/libc/sparc/Makefile index c67463652b..37e0818dc6 100644 --- a/usr/src/lib/libc/sparc/Makefile +++ b/usr/src/lib/libc/sparc/Makefile @@ -102,6 +102,9 @@ FPASMOBJS= \ ATOMICOBJS= \ atomic.o +XATTROBJS= \ + xattr_common.o + COMOBJS= \ bcmp.o \ bcopy.o \ @@ -413,6 +416,7 @@ PORTGEN= \ atoi.o \ atol.o \ atoll.o \ + attrat.o \ attropen.o \ atexit.o \ atfork.o \ @@ -912,6 +916,7 @@ MOSTOBJS= \ $(FPOBJS) \ $(FPASMOBJS) \ $(ATOMICOBJS) \ + $(XATTROBJS) \ $(COMOBJS) \ $(DTRACEOBJS) \ $(GENOBJS) \ @@ -1044,6 +1049,7 @@ CLOBBERFILES += $(LIB_PIC) # list of C source for lint SRCS= \ $(ATOMICOBJS:%.o=$(SRC)/common/atomic/%.c) \ + $(XATTROBJS:%.o=$(SRC)/common/xattr/%.c) \ $(COMOBJS:%.o=$(SRC)/common/util/%.c) \ $(DTRACEOBJS:%.o=$(SRC)/common/dtrace/%.c) \ $(PORTFP:%.o=../port/fp/%.c) \ diff --git a/usr/src/lib/libc/sparcv9/Makefile b/usr/src/lib/libc/sparcv9/Makefile index c114126bfe..c837e39361 100644 --- a/usr/src/lib/libc/sparcv9/Makefile +++ b/usr/src/lib/libc/sparcv9/Makefile @@ -107,6 +107,9 @@ $(__GNUC)FPASMOBJS += \ ATOMICOBJS= \ atomic.o +XATTROBJS= \ + xattr_common.o + COMOBJS= \ bcmp.o \ bcopy.o \ @@ -371,6 +374,7 @@ PORTGEN= \ abort.o \ addsev.o \ assert.o \ + attrat.o \ atof.o \ atoi.o \ atol.o \ @@ -858,6 +862,7 @@ MOSTOBJS= \ $(FPOBJS64) \ $(FPASMOBJS) \ $(ATOMICOBJS) \ + $(XATTROBJS) \ $(COMOBJS) \ $(GENOBJS) \ $(PORTFP) \ @@ -978,6 +983,7 @@ CLOBBERFILES += $(LIB_PIC) # list of C source for lint SRCS= \ $(ATOMICOBJS:%.o=$(SRC)/common/atomic/%.c) \ + $(XATTROBJS:%.o=$(SRC)/common/xattr/%.c) \ $(COMOBJS:%.o=$(SRC)/common/util/%.c) \ $(PORTFP:%.o=../port/fp/%.c) \ $(PORTGEN:%.o=../port/gen/%.c) \ diff --git a/usr/src/lib/libcmdutils/Makefile.com b/usr/src/lib/libcmdutils/Makefile.com index 50ca57cedf..e12e5b07c5 100644 --- a/usr/src/lib/libcmdutils/Makefile.com +++ b/usr/src/lib/libcmdutils/Makefile.com @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -27,14 +27,14 @@ LIBRARY= libcmdutils.a VERS= .1 -OBJECTS= avltree.o +OBJECTS= avltree.o sysattrs.o writefile.o process_xattrs.o include ../../Makefile.lib include ../../Makefile.rootfs LIBS = $(DYNLIB) $(LINTLIB) -LDLIBS += -lc -lavl +LDLIBS += -lc -lavl -lnvpair SRCDIR = ../common $(LINTLIB) := SRCS = $(SRCDIR)/$(LINTSRC) @@ -43,7 +43,7 @@ CFLAGS += $(CCVERBOSE) # All commands using the common avltree interfaces must # be largefile aware. -CPPFLAGS += -I.. -D_REENTRANT -D_FILE_OFFSET_BITS=64 +CPPFLAGS += -I.. -I../../common/inc -D_REENTRANT -D_FILE_OFFSET_BITS=64 .KEEP_STATE: diff --git a/usr/src/lib/libcmdutils/common/mapfile-vers b/usr/src/lib/libcmdutils/common/mapfile-vers index 48d26ca9af..295493845b 100644 --- a/usr/src/lib/libcmdutils/common/mapfile-vers +++ b/usr/src/lib/libcmdutils/common/mapfile-vers @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -30,6 +30,12 @@ SUNWprivate_1.1 { add_tnode; destroy_tree; tnode_compare; + sysattr_type; + sysattr_support; + writefile; + get_attrdirs; + mv_xattrs; + sysattr_list; local: *; }; diff --git a/usr/src/lib/libcmdutils/common/process_xattrs.c b/usr/src/lib/libcmdutils/common/process_xattrs.c new file mode 100644 index 0000000000..5c8df946a1 --- /dev/null +++ b/usr/src/lib/libcmdutils/common/process_xattrs.c @@ -0,0 +1,334 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <c_synonyms.h> +#include "libcmdutils.h" + + +/* + * Gets file descriptors of attribute directories for source and target + * attribute files + */ +int +get_attrdirs(int indfd, int outdfd, char *attrfile, int *sfd, int *tfd) +{ + int pwdfd; + int fd1; + int fd2; + + pwdfd = open(".", O_RDONLY); + if ((pwdfd != -1) && (fchdir(indfd) == 0)) { + if ((fd1 = attropen(attrfile, ".", O_RDONLY)) == -1) { + (void) fchdir(pwdfd); + (void) close(pwdfd); + return (1); + } + *sfd = fd1; + } else { + (void) fchdir(pwdfd); + (void) close(pwdfd); + return (1); + } + if (fchdir(outdfd) == 0) { + if ((fd2 = attropen(attrfile, ".", O_RDONLY)) == -1) { + (void) fchdir(pwdfd); + (void) close(pwdfd); + return (1); + } + *tfd = fd2; + } else { + (void) fchdir(pwdfd); + (void) close(pwdfd); + return (1); + } + (void) fchdir(pwdfd); + return (0); +} + +/* + * mv_xattrs - Copies the content of the extended attribute files. Then + * moves the extended system attributes from the input attribute files + * to the target attribute files. Moves the extended system attributes + * from source to the target file. This function returns 0 on success + * and nonzero on error. + */ +int +mv_xattrs(char *cmd, char *infile, char *outfile, int sattr, int silent) +{ + int srcfd = -1; + int indfd = -1; + int outdfd = -1; + int tmpfd = -1; + int sattrfd = -1; + int tattrfd = -1; + int asfd = -1; + int atfd = -1; + DIR *dirp = NULL; + struct dirent *dp = NULL; + char *etext = NULL; + struct stat st1; + struct stat st2; + nvlist_t *response = NULL; + nvlist_t *res = NULL; + + if ((srcfd = open(infile, O_RDONLY)) == -1) { + etext = dgettext(TEXT_DOMAIN, "cannot open source"); + goto error; + } + if (sattr) + response = sysattr_list(cmd, srcfd, infile); + + if ((indfd = openat(srcfd, ".", O_RDONLY|O_XATTR)) == -1) { + etext = dgettext(TEXT_DOMAIN, "cannot openat source"); + goto error; + } + if ((outdfd = attropen(outfile, ".", O_RDONLY)) == -1) { + etext = dgettext(TEXT_DOMAIN, "cannot attropen target"); + goto error; + } + if ((tmpfd = dup(indfd)) == -1) { + etext = dgettext(TEXT_DOMAIN, "cannot dup descriptor"); + goto error; + + } + if ((dirp = fdopendir(tmpfd)) == NULL) { + etext = dgettext(TEXT_DOMAIN, "cannot access source"); + goto error; + } + while ((dp = readdir(dirp)) != NULL) { + if ((dp->d_name[0] == '.' && dp->d_name[1] == '\0') || + (dp->d_name[0] == '.' && dp->d_name[1] == '.' && + dp->d_name[2] == '\0') || + (sysattr_type(dp->d_name) == _RO_SATTR) || + (sysattr_type(dp->d_name) == _RW_SATTR)) + continue; + + if ((sattrfd = openat(indfd, dp->d_name, + O_RDONLY)) == -1) { + etext = dgettext(TEXT_DOMAIN, + "cannot open src attribute file"); + goto error; + } + if (fstat(sattrfd, &st1) < 0) { + etext = dgettext(TEXT_DOMAIN, + "could not stat attribute file"); + goto error; + } + if ((tattrfd = openat(outdfd, dp->d_name, + O_RDWR|O_CREAT|O_TRUNC, st1.st_mode)) == -1) { + etext = dgettext(TEXT_DOMAIN, + "cannot open target attribute file"); + goto error; + } + if (fstat(tattrfd, &st2) < 0) { + etext = dgettext(TEXT_DOMAIN, + "could not stat attribute file"); + goto error; + } + if (writefile(sattrfd, tattrfd, infile, outfile, dp->d_name, + dp->d_name, &st1, &st2) != 0) { + etext = dgettext(TEXT_DOMAIN, + "failed to copy extended attribute " + "from source to target"); + goto error; + } + + errno = 0; + if (sattr) { + /* + * Gets non default extended system attributes from + * source to copy to target. + */ + if (dp->d_name != NULL) + res = sysattr_list(cmd, sattrfd, dp->d_name); + + if (res != NULL && + get_attrdirs(indfd, outdfd, dp->d_name, &asfd, + &atfd) != 0) { + etext = dgettext(TEXT_DOMAIN, + "Failed to open attribute files"); + goto error; + } + /* + * Copy extended system attribute from source + * attribute file to target attribute file + */ + if (res != NULL && + (renameat(asfd, VIEW_READWRITE, atfd, + VIEW_READWRITE) != 0)) { + if (errno == EPERM) + etext = dgettext(TEXT_DOMAIN, + "Permission denied -" + "failed to move system attribute"); + else + etext = dgettext(TEXT_DOMAIN, + "failed to move extended " + "system attribute"); + goto error; + } + } + if (sattrfd != -1) + (void) close(sattrfd); + if (tattrfd != -1) + (void) close(tattrfd); + if (asfd != -1) + (void) close(asfd); + if (atfd != -1) + (void) close(atfd); + if (res != NULL) { + nvlist_free(res); + res = NULL; + } + } + errno = 0; + /* Copy extended system attribute from source to target */ + + if (response != NULL) { + if (renameat(indfd, VIEW_READWRITE, outdfd, + VIEW_READWRITE) == 0) + goto done; + + if (errno == EPERM) + etext = dgettext(TEXT_DOMAIN, "Permission denied"); + else + etext = dgettext(TEXT_DOMAIN, + "failed to move system attribute"); + } +error: + if (res != NULL) + nvlist_free(res); + if (silent == 0 && etext != NULL) { + if (!sattr) + (void) fprintf(stderr, dgettext(TEXT_DOMAIN, + "%s: %s: cannot move extended attributes, "), + cmd, infile); + else + (void) fprintf(stderr, dgettext(TEXT_DOMAIN, + "%s: %s: cannot move extended system " + "attributes, "), cmd, infile); + perror(etext); + } +done: + if (dirp) + (void) closedir(dirp); + if (sattrfd != -1) + (void) close(sattrfd); + if (tattrfd != -1) + (void) close(tattrfd); + if (asfd != -1) + (void) close(asfd); + if (atfd != -1) + (void) close(atfd); + if (indfd != -1) + (void) close(indfd); + if (outdfd != -1) + (void) close(outdfd); + if (response != NULL) + nvlist_free(response); + if (etext != NULL) + return (1); + else + return (0); +} + +/* + * The function returns non default extended system attribute list + * associated with 'fname' and returns NULL when an error has occured + * or when only extended system attributes other than archive, + * av_modified or crtime are set. + * + * The function returns system attribute list for the following cases: + * + * - any extended system attribute other than the default attributes + * ('archive', 'av_modified' and 'crtime') is set + * - nvlist has NULL name string + * - nvpair has data type of 'nvlist' + * - default data type. + */ + +nvlist_t * +sysattr_list(char *cmd, int fd, char *fname) +{ + boolean_t value; + data_type_t type; + nvlist_t *response; + nvpair_t *pair; + f_attr_t fattr; + char *name; + + if (nvlist_alloc(&response, NV_UNIQUE_NAME, 0) != 0) { + (void) fprintf(stderr, dgettext(TEXT_DOMAIN, + "%s: %s: nvlist_alloc failed\n"), + cmd, fname); + return (NULL); + } + if (fgetattr(fd, XATTR_VIEW_READWRITE, &response) != 0) { + (void) fprintf(stderr, dgettext(TEXT_DOMAIN, + "%s: %s: fgetattr failed\n"), + cmd, fname); + nvlist_free(response); + return (NULL); + } + pair = NULL; + while ((pair = nvlist_next_nvpair(response, pair)) != NULL) { + + name = nvpair_name(pair); + + if (name != NULL) + fattr = name_to_attr(name); + else + return (response); + + type = nvpair_type(pair); + switch (type) { + case DATA_TYPE_BOOLEAN_VALUE: + if (nvpair_value_boolean_value(pair, + &value) != 0) { + (void) fprintf(stderr, + dgettext(TEXT_DOMAIN, "%s " + "nvpair_value_boolean_value " + "failed\n"), cmd); + continue; + } + if (value && fattr != F_ARCHIVE && + fattr != F_AV_MODIFIED) + return (response); + break; + case DATA_TYPE_UINT64_ARRAY: + if (fattr != F_CRTIME) + return (response); + break; + case DATA_TYPE_NVLIST: + default: + return (response); + break; + } + } + if (response != NULL) + nvlist_free(response); + return (NULL); +} diff --git a/usr/src/lib/libcmdutils/common/sysattrs.c b/usr/src/lib/libcmdutils/common/sysattrs.c new file mode 100644 index 0000000000..a22e8e5e21 --- /dev/null +++ b/usr/src/lib/libcmdutils/common/sysattrs.c @@ -0,0 +1,128 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <c_synonyms.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <libgen.h> +#include <attr.h> +#include <fcntl.h> +#include <errno.h> +#include "libcmdutils.h" + +/* + * Returns the status of attempting to obtain the extended system + * attributes in the specified view. + * + * Note: If obtaining status for an extended attribute file, the caller must + * chdir into the hidden directory prior to calling sysattr_status(). + * + * Returns 1 if the extended system attributes were obtained, otherwise + * returns 0. + */ +int +sysattr_status(char *file, xattr_view_t view) +{ + nvlist_t *response; + int saveerrno; + int status; + + if (nvlist_alloc(&response, NV_UNIQUE_NAME, 0) != 0) { + return (0); + } + + status = getattrat(AT_FDCWD, view, file, &response); + + saveerrno = errno; + (void) nvlist_free(response); + errno = saveerrno; + + return (status == 0); +} + +/* + * Returns the type of the specified in file. If the file name matches + * the name of either a read-only or read-write extended system attribute + * file then sysattr_type() returns the type of file: + * return value file type + * ------------ --------- + * _RO_SATTR read-only extended system attribute file + * _RW_SATTR read-write extended system attribute file + * _NOT_SATTR neither a read-only or read-write extended system + * attribute file. + */ +int +sysattr_type(char *file) +{ + if (file == NULL) { + errno = ENOENT; + return (_NOT_SATTR); + } + + if (strcmp(basename(file), file) != 0) { + errno = EINVAL; + return (_NOT_SATTR); + } + + errno = 0; + if (strcmp(file, VIEW_READONLY) == 0) { + return (_RO_SATTR); + } else if (strcmp(file, VIEW_READWRITE) == 0) { + return (_RW_SATTR); + } else { + return (_NOT_SATTR); + } +} + +/* + * Call sysattr_support() instead of pathconf(file, _PC_SATTR_ENABLED) or + * pathconf(file, _PC_SATTR_EXISTS) so that if pathconf() fails over NFS, we + * can still try to figure out if extended system attributes are supported by + * testing for a valid extended system attribute file. + * + * 'name' can have the values _PC_SATTR_ENABLED or _PC_SATTR_EXISTS. + * + * Returns 1 if the underlying file system supports extended system attributes, + * otherwise, returns -1. + */ +int +sysattr_support(char *file, int name) +{ + int rc; + + errno = 0; + if ((name != _PC_SATTR_ENABLED) && + (name != _PC_SATTR_EXISTS)) { + errno = EINVAL; + return (-1); + } + if (((rc = pathconf(file, name)) == 1) || (errno != EINVAL)) { + return (rc); + } + return (sysattr_status(file, XATTR_VIEW_READONLY)); +} diff --git a/usr/src/lib/libcmdutils/common/writefile.c b/usr/src/lib/libcmdutils/common/writefile.c new file mode 100644 index 0000000000..3e36f90c61 --- /dev/null +++ b/usr/src/lib/libcmdutils/common/writefile.c @@ -0,0 +1,230 @@ +/* + * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + +/* + * University Copyright- Copyright (c) 1982, 1986, 1988 + * The Regents of the University of California + * All Rights Reserved + * + * University Acknowledgment- Portions of this document are derived from + * software developed by the University of California, Berkeley, and its + * contributors. + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <c_synonyms.h> +#include "libcmdutils.h" + + +int +writefile(int fi, int fo, char *infile, char *outfile, char *asfile, + char *atfile, struct stat *s1p, struct stat *s2p) +{ + int mapsize, munmapsize; + caddr_t cp; + off_t filesize = s1p->st_size; + off_t offset; + int nbytes; + int remains; + int n; + size_t src_size; + size_t targ_size; + char *srcbuf; + char *targbuf; + + if (asfile != NULL) { + src_size = strlen(infile) + strlen(asfile) + + strlen(dgettext(TEXT_DOMAIN, " attribute ")) + 1; + } else { + src_size = strlen(infile) + 1; + } + srcbuf = malloc(src_size); + if (srcbuf == NULL) { + (void) fprintf(stderr, + dgettext(TEXT_DOMAIN, "could not allocate memory" + " for path buffer: ")); + return (1); + } + if (asfile != NULL) { + (void) snprintf(srcbuf, src_size, "%s%s%s", + infile, dgettext(TEXT_DOMAIN, " attribute "), asfile); + } else { + (void) snprintf(srcbuf, src_size, "%s", infile); + } + + if (atfile != NULL) { + targ_size = strlen(outfile) + strlen(atfile) + + strlen(dgettext(TEXT_DOMAIN, " attribute ")) + 1; + } else { + targ_size = strlen(outfile) + 1; + } + targbuf = malloc(targ_size); + if (targbuf == NULL) { + (void) fprintf(stderr, + dgettext(TEXT_DOMAIN, "could not allocate memory" + " for path buffer: ")); + return (1); + } + if (atfile != NULL) { + (void) snprintf(targbuf, targ_size, "%s%s%s", + outfile, dgettext(TEXT_DOMAIN, " attribute "), atfile); + } else { + (void) snprintf(targbuf, targ_size, "%s", outfile); + } + + if (ISREG(*s1p) && s1p->st_size > SMALLFILESIZE) { + /* + * Determine size of initial mapping. This will determine the + * size of the address space chunk we work with. This initial + * mapping size will be used to perform munmap() in the future. + */ + mapsize = MAXMAPSIZE; + if (s1p->st_size < mapsize) mapsize = s1p->st_size; + munmapsize = mapsize; + + /* + * Mmap time! + */ + if ((cp = mmap((caddr_t)NULL, mapsize, PROT_READ, + MAP_SHARED, fi, (off_t)0)) == MAP_FAILED) + mapsize = 0; /* can't mmap today */ + } else + mapsize = 0; + + if (mapsize != 0) { + offset = 0; + + for (;;) { + nbytes = write(fo, cp, mapsize); + /* + * if we write less than the mmaped size it's due to a + * media error on the input file or out of space on + * the output file. So, try again, and look for errno. + */ + if ((nbytes >= 0) && (nbytes != (int)mapsize)) { + remains = mapsize - nbytes; + while (remains > 0) { + nbytes = write(fo, + cp + mapsize - remains, remains); + if (nbytes < 0) { + if (errno == ENOSPC) + perror(targbuf); + else + perror(srcbuf); + (void) close(fi); + (void) close(fo); + (void) munmap(cp, munmapsize); + if (ISREG(*s2p)) + (void) unlink(targbuf); + return (1); + } + remains -= nbytes; + if (remains == 0) + nbytes = mapsize; + } + } + /* + * although the write manual page doesn't specify this + * as a possible errno, it is set when the nfs read + * via the mmap'ed file is accessed, so report the + * problem as a source access problem, not a target file + * problem + */ + if (nbytes < 0) { + if (errno == EACCES) + perror(srcbuf); + else + perror(targbuf); + (void) close(fi); + (void) close(fo); + (void) munmap(cp, munmapsize); + if (ISREG(*s2p)) + (void) unlink(targbuf); + if (srcbuf != NULL) + free(srcbuf); + if (targbuf != NULL) + free(targbuf); + return (1); + } + filesize -= nbytes; + if (filesize == 0) + break; + offset += nbytes; + if (filesize < mapsize) + mapsize = filesize; + if (mmap(cp, mapsize, PROT_READ, MAP_SHARED | + MAP_FIXED, fi, offset) == MAP_FAILED) { + perror(srcbuf); + (void) close(fi); + (void) close(fo); + (void) munmap(cp, munmapsize); + if (ISREG(*s2p)) + (void) unlink(targbuf); + if (srcbuf != NULL) + free(srcbuf); + if (targbuf != NULL) + free(targbuf); + return (1); + } + } + (void) munmap(cp, munmapsize); + } else { + char buf[SMALLFILESIZE]; + for (;;) { + n = read(fi, buf, sizeof (buf)); + if (n == 0) { + return (0); + } else if (n < 0) { + (void) close(fi); + (void) close(fo); + if (ISREG(*s2p)) + (void) unlink(targbuf); + if (srcbuf != NULL) + free(srcbuf); + if (targbuf != NULL) + free(targbuf); + return (1); + } else if (write(fo, buf, n) != n) { + (void) close(fi); + (void) close(fo); + if (ISREG(*s2p)) + (void) unlink(targbuf); + if (srcbuf != NULL) + free(srcbuf); + if (targbuf != NULL) + free(targbuf); + return (1); + } + } + } + if (srcbuf != NULL) + free(srcbuf); + if (targbuf != NULL) + free(targbuf); + return (0); +} diff --git a/usr/src/lib/libcmdutils/libcmdutils.h b/usr/src/lib/libcmdutils/libcmdutils.h index 28a9e996d4..d1c3a0b193 100644 --- a/usr/src/lib/libcmdutils/libcmdutils.h +++ b/usr/src/lib/libcmdutils/libcmdutils.h @@ -2,9 +2,8 @@ * CDDL HEADER START * * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License"). You may not use this file except in compliance - * with the License. + * 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. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -38,14 +37,35 @@ * this file. */ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <libintl.h> +#include <string.h> +#include <dirent.h> +#include <attr.h> #include <sys/avl.h> #include <sys/types.h> -#include <stdlib.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <libnvpair.h> #ifdef __cplusplus extern "C" { #endif +/* extended system attribute support */ +#define _NOT_SATTR 0 +#define _RO_SATTR 1 +#define _RW_SATTR 2 + +#define MAXMAPSIZE (1024*1024*8) /* map at most 8MB */ +#define SMALLFILESIZE (32*1024) /* don't use mmap on little file */ +#define ISREG(A) (((A).st_mode & S_IFMT) == S_IFREG) + /* avltree */ #define OFFSETOF(s, m) ((size_t)(&(((s *)0)->m))) @@ -56,11 +76,38 @@ typedef struct tree_node { avl_node_t avl_link; } tree_node_t; + + /* extended system attribute support */ + +/* Determine if a file is the name of an extended system attribute file */ +extern int sysattr_type(char *); + +/* Determine if the underlying file system supports system attributes */ +extern int sysattr_support(char *, int); + +/* Copies the content of the source file to the target file */ +extern int writefile(int, int, char *, char *, char *, char *, +struct stat *, struct stat *); + +/* Gets file descriptors of the source and target attribute files */ +extern int get_attrdirs(int, int, char *, int *, int *); + +/* Move extended attribute and extended system attribute */ +extern int mv_xattrs(char *, char *, char *, int, int); + +/* Returns non default extended system attribute list */ +extern nvlist_t *sysattr_list(char *, int, char *); + + + + /* avltree */ + /* * Used to compare two nodes. We are attempting to match the 1st * argument (node) against the 2nd argument (a node which * is already in the search tree). */ + extern int tnode_compare(const void *, const void *); /* diff --git a/usr/src/lib/libsec/common/acl.y b/usr/src/lib/libsec/common/acl.y index 684ab0ba3d..8b5d29509e 100644 --- a/usr/src/lib/libsec/common/acl.y +++ b/usr/src/lib/libsec/common/acl.y @@ -19,13 +19,13 @@ * * CDDL HEADER END * - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" -#include <sys/acl.h> +#include <acl_common.h> #include <aclutils.h> extern int yyinteractive; diff --git a/usr/src/lib/libsec/common/acl_lex.l b/usr/src/lib/libsec/common/acl_lex.l index ce08a1da28..890c063cc7 100644 --- a/usr/src/lib/libsec/common/acl_lex.l +++ b/usr/src/lib/libsec/common/acl_lex.l @@ -59,6 +59,7 @@ int yybufpos; %} +%e 1500 %s TS NS PS AIS AS US ES /* * TS = type state @@ -73,7 +74,7 @@ int yybufpos; ID [0-9]+ LOGNAME [^:]+: PERM_STR [rRwWxpdDaAcCos-]+ -INHERIT_STR [fdinFS-]+ +INHERIT_STR [fdinFSI-]+ %% @@ -398,6 +399,32 @@ INHERIT_STR [fdinFS-]+ yylval.val = ACE_ACCESS_DENIED_ACE_TYPE; return (ACCESS_TYPE); } +<AS>audit/[:,\n] { + int c; + + c = input(); + unput(c); + if (c == ',' || c == '\n') + BEGIN ES; + else + BEGIN US; + + yylval.val = ACE_SYSTEM_AUDIT_ACE_TYPE; + return (ACCESS_TYPE); + } +<AS>alarm/[:,\n] { + int c; + + c = input(); + unput(c); + if (c == ',' || c == '\n') + BEGIN ES; + else + BEGIN US; + + yylval.val = ACE_SYSTEM_ALARM_ACE_TYPE; + return (ACCESS_TYPE); + } <AS>: { acl_error(dgettext(TEXT_DOMAIN, @@ -466,6 +493,33 @@ INHERIT_STR [fdinFS-]+ yylval.val = ACE_ACCESS_DENIED_ACE_TYPE; return (ACCESS_TYPE); } +<AIS>audit/[:,\n] { + int c; + + c = input(); + unput(c); + if (c == ',' || c == '\n') + BEGIN ES; + else + BEGIN US; + + yylval.val = ACE_SYSTEM_AUDIT_ACE_TYPE; + return (ACCESS_TYPE); + } +<AIS>alarm/[:,\n] { + + int c; + + c = input(); + unput(c); + if (c == ',' || c == '\n') + BEGIN ES; + else + BEGIN US; + + yylval.val = ACE_SYSTEM_ALARM_ACE_TYPE; + return (ACCESS_TYPE); + } <AIS>file_inherit/[:/,] { yylval.val = ACE_FILE_INHERIT_ACE; return (ACE_INHERIT); @@ -482,6 +536,19 @@ INHERIT_STR [fdinFS-]+ yylval.val = ACE_INHERIT_ONLY_ACE; return (ACE_INHERIT); } + +<AIS>successful_access/[/:,] { + yylval.val = ACE_SUCCESSFUL_ACCESS_ACE_FLAG; + return (ACE_INHERIT); + } +<AIS>failed_access/[/:,] { + yylval.val = ACE_FAILED_ACCESS_ACE_FLAG; + return (ACE_INHERIT); + } +<AIS>inherited/[/:,] { + yylval.val = ACE_INHERITED_ACE; + return (ACE_INHERIT); + } <AIS>{INHERIT_STR}/[:] { yylval.str = strdup(yytext); if (yylval.str == NULL) { @@ -625,7 +692,7 @@ INHERIT_STR [fdinFS-]+ * used for retrieving illegal data in ACL specification. * * The first set of characters is retrieved from yytext. - * subseequent characters are pulled from the input stream, + * subsequent characters are pulled from the input stream, * until either EOF or one of the requested terminators is scene. * Result is returned in yylval.str which is malloced. */ @@ -783,7 +850,7 @@ acl_str_to_id(char *str, int *id) uid_t value; errno = 0; - value = strtol(str, &end, 10); + value = strtoul(str, &end, 10); if (errno != 0 || *end != '\0') return (EACL_INVALID_USER_GROUP); diff --git a/usr/src/lib/libsec/common/acltext.c b/usr/src/lib/libsec/common/acltext.c index cdfd171c82..c0e1bb1e58 100644 --- a/usr/src/lib/libsec/common/acltext.c +++ b/usr/src/lib/libsec/common/acltext.c @@ -540,8 +540,12 @@ ace_inherit_txt(char *buf, char **endp, uint32_t iflags, int flags) buf[5] = 'F'; else buf[5] = '-'; - buf[6] = '\0'; - *endp = buf + 6; + if (iflags & ACE_INHERITED_ACE) + buf[6] = 'I'; + else + buf[6] = '-'; + buf[7] = '\0'; + *endp = buf + 7; } else { if (iflags & ACE_FILE_INHERIT_ACE) { strcpy(lend, "file_inherit/"); @@ -559,6 +563,18 @@ ace_inherit_txt(char *buf, char **endp, uint32_t iflags, int flags) strcpy(lend, "inherit_only/"); lend += sizeof ("inherit_only/") - 1; } + if (iflags & ACE_SUCCESSFUL_ACCESS_ACE_FLAG) { + strcpy(lend, "successful_access/"); + lend += sizeof ("successful_access/") - 1; + } + if (iflags & ACE_FAILED_ACCESS_ACE_FLAG) { + strcpy(lend, "failed_access/"); + lend += sizeof ("failed_access/") - 1; + } + if (iflags & ACE_INHERITED_ACE) { + strcpy(lend, "inherited/"); + lend += sizeof ("inherited/") - 1; + } if (*(lend - 1) == '/') *--lend = '\0'; @@ -829,16 +845,19 @@ increase_length(struct dynaclstr *dacl, size_t increase) * The length of a perms entry is 144 i.e read_data/write_data... * to each acl entry. * - * iflags: file_inherit/dir_inherit/inherit_only/no_propagate + * iflags: file_inherit/dir_inherit/inherit_only/no_propagate/successful_access + * /failed_access * */ #define ACE_ENTRYTYPLEN 6 -#define IFLAGS_SIZE 51 +#define IFLAGS_STR "file_inherit/dir_inherit/inherit_only/no_propagate/" \ + "successful_access/failed_access/inherited" +#define IFLAGS_SIZE (sizeof (IFLAGS_STR) - 1) #define ACCESS_TYPE_SIZE 7 /* if unknown */ #define COLON_CNT 3 #define PERMS_LEN 216 -#define ACE_ENTRY_SIZE (ACE_ENTRYTYPLEN + ID_STR_MAX + PERMS_LEN +\ +#define ACE_ENTRY_SIZE (ACE_ENTRYTYPLEN + ID_STR_MAX + PERMS_LEN + \ ACCESS_TYPE_SIZE + IFLAGS_SIZE + COLON_CNT + APPENDED_ID_MAX) static char * @@ -871,7 +890,9 @@ ace_acltotext(acl_t *aceaclp, int flags) (void) ace_inherit_txt(endp, &endp, aclp->a_flags, flags); if (flags & ACL_COMPACT_FMT || aclp->a_flags & (ACE_FILE_INHERIT_ACE | ACE_DIRECTORY_INHERIT_ACE | - (ACE_INHERIT_ONLY_ACE | ACE_NO_PROPAGATE_INHERIT_ACE))) { + (ACE_INHERIT_ONLY_ACE | ACE_NO_PROPAGATE_INHERIT_ACE | + ACE_INHERITED_ACE | ACE_SUCCESSFUL_ACCESS_ACE_FLAG | + ACE_FAILED_ACCESS_ACE_FLAG))) { *endp++ = ':'; *endp = '\0'; } @@ -972,7 +993,7 @@ ace_compact_printacl(acl_t *aclp) aclp->acl_flags & ACL_IS_DIR, ACL_COMPACT_FMT)); (void) printf("%s:", ace_inherit_txt(endp, &endp, acep->a_flags, - ACL_COMPACT_FMT)); + ACL_COMPACT_FMT)); (void) printf("%s\n", ace_access_txt(endp, &endp, acep->a_type)); } @@ -1038,16 +1059,14 @@ typedef struct value_table { uint32_t p_value; /* value for perm when pletter found */ } value_table_t; -#define ACE_PERM_COUNT 14 - /* - * The permission tables are layed out in positional order + * The permission tables are laid out in positional order * a '-' character will indicate a permission at a given * position is not specified. The '-' is not part of the * table, but will be checked for in the permission computation * routine. */ -value_table_t ace_perm_table[ACE_PERM_COUNT] = { +value_table_t ace_perm_table[] = { { 'r', ACE_READ_DATA}, { 'w', ACE_WRITE_DATA}, { 'x', ACE_EXECUTE}, @@ -1064,24 +1083,28 @@ value_table_t ace_perm_table[ACE_PERM_COUNT] = { { 's', ACE_SYNCHRONIZE} }; -#define ACLENT_PERM_COUNT 3 +#define ACE_PERM_COUNT (sizeof (ace_perm_table) / sizeof (value_table_t)) -value_table_t aclent_perm_table[ACLENT_PERM_COUNT] = { +value_table_t aclent_perm_table[] = { { 'r', S_IROTH}, { 'w', S_IWOTH}, { 'x', S_IXOTH} }; -#define IFLAG_COUNT 6 -value_table_t inherit_table[IFLAG_COUNT] = { +#define ACLENT_PERM_COUNT (sizeof (aclent_perm_table) / sizeof (value_table_t)) + +value_table_t inherit_table[] = { {'f', ACE_FILE_INHERIT_ACE}, {'d', ACE_DIRECTORY_INHERIT_ACE}, {'i', ACE_INHERIT_ONLY_ACE}, {'n', ACE_NO_PROPAGATE_INHERIT_ACE}, {'S', ACE_SUCCESSFUL_ACCESS_ACE_FLAG}, - {'F', ACE_FAILED_ACCESS_ACE_FLAG} + {'F', ACE_FAILED_ACCESS_ACE_FLAG}, + {'I', ACE_INHERITED_ACE} }; +#define IFLAG_COUNT (sizeof (inherit_table) / sizeof (value_table_t)) + /* * compute value from a permission table or inheritance table * based on string passed in. If positional is set then diff --git a/usr/src/lib/libsec/common/aclutils.c b/usr/src/lib/libsec/common/aclutils.c index d90ad4b171..0200db6c66 100644 --- a/usr/src/lib/libsec/common/aclutils.c +++ b/usr/src/lib/libsec/common/aclutils.c @@ -33,171 +33,23 @@ #include <pwd.h> #include <strings.h> #include <sys/types.h> -#include <sys/acl.h> #include <errno.h> #include <sys/stat.h> #include <sys/varargs.h> #include <locale.h> #include <aclutils.h> -#include <acl_common.h> #include <sys/avl.h> - -#define offsetof(s, m) ((size_t)(&(((s *)0)->m))) +#include <acl_common.h> #define ACL_PATH 0 #define ACL_FD 1 -#define ACE_POSIX_SUPPORTED_BITS (ACE_READ_DATA | \ - ACE_WRITE_DATA | ACE_APPEND_DATA | ACE_EXECUTE | \ - ACE_READ_ATTRIBUTES | ACE_READ_ACL | ACE_WRITE_ACL) - - -#define ACL_SYNCHRONIZE_SET_DENY 0x0000001 -#define ACL_SYNCHRONIZE_SET_ALLOW 0x0000002 -#define ACL_SYNCHRONIZE_ERR_DENY 0x0000004 -#define ACL_SYNCHRONIZE_ERR_ALLOW 0x0000008 - -#define ACL_WRITE_OWNER_SET_DENY 0x0000010 -#define ACL_WRITE_OWNER_SET_ALLOW 0x0000020 -#define ACL_WRITE_OWNER_ERR_DENY 0x0000040 -#define ACL_WRITE_OWNER_ERR_ALLOW 0x0000080 - -#define ACL_DELETE_SET_DENY 0x0000100 -#define ACL_DELETE_SET_ALLOW 0x0000200 -#define ACL_DELETE_ERR_DENY 0x0000400 -#define ACL_DELETE_ERR_ALLOW 0x0000800 - -#define ACL_WRITE_ATTRS_OWNER_SET_DENY 0x0001000 -#define ACL_WRITE_ATTRS_OWNER_SET_ALLOW 0x0002000 -#define ACL_WRITE_ATTRS_OWNER_ERR_DENY 0x0004000 -#define ACL_WRITE_ATTRS_OWNER_ERR_ALLOW 0x0008000 - -#define ACL_WRITE_ATTRS_WRITER_SET_DENY 0x0010000 -#define ACL_WRITE_ATTRS_WRITER_SET_ALLOW 0x0020000 -#define ACL_WRITE_ATTRS_WRITER_ERR_DENY 0x0040000 -#define ACL_WRITE_ATTRS_WRITER_ERR_ALLOW 0x0080000 - -#define ACL_WRITE_NAMED_WRITER_SET_DENY 0x0100000 -#define ACL_WRITE_NAMED_WRITER_SET_ALLOW 0x0200000 -#define ACL_WRITE_NAMED_WRITER_ERR_DENY 0x0400000 -#define ACL_WRITE_NAMED_WRITER_ERR_ALLOW 0x0800000 - -#define ACL_READ_NAMED_READER_SET_DENY 0x1000000 -#define ACL_READ_NAMED_READER_SET_ALLOW 0x2000000 -#define ACL_READ_NAMED_READER_ERR_DENY 0x4000000 -#define ACL_READ_NAMED_READER_ERR_ALLOW 0x8000000 - - -#define ACE_VALID_MASK_BITS (\ - ACE_READ_DATA | \ - ACE_LIST_DIRECTORY | \ - ACE_WRITE_DATA | \ - ACE_ADD_FILE | \ - ACE_APPEND_DATA | \ - ACE_ADD_SUBDIRECTORY | \ - ACE_READ_NAMED_ATTRS | \ - ACE_WRITE_NAMED_ATTRS | \ - ACE_EXECUTE | \ - ACE_DELETE_CHILD | \ - ACE_READ_ATTRIBUTES | \ - ACE_WRITE_ATTRIBUTES | \ - ACE_DELETE | \ - ACE_READ_ACL | \ - ACE_WRITE_ACL | \ - ACE_WRITE_OWNER | \ - ACE_SYNCHRONIZE) - -#define ACE_MASK_UNDEFINED 0x80000000 - -#define ACE_VALID_FLAG_BITS (ACE_FILE_INHERIT_ACE | \ - ACE_DIRECTORY_INHERIT_ACE | \ - ACE_NO_PROPAGATE_INHERIT_ACE | ACE_INHERIT_ONLY_ACE | \ - ACE_SUCCESSFUL_ACCESS_ACE_FLAG | ACE_FAILED_ACCESS_ACE_FLAG | \ - ACE_IDENTIFIER_GROUP | ACE_OWNER | ACE_GROUP | ACE_EVERYONE) - -/* - * ACL conversion helpers - */ - -typedef enum { - ace_unused, - ace_user_obj, - ace_user, - ace_group, /* includes GROUP and GROUP_OBJ */ - ace_other_obj -} ace_to_aent_state_t; - -typedef struct acevals { - uid_t key; - avl_node_t avl; - uint32_t mask; - uint32_t allowed; - uint32_t denied; - int aent_type; -} acevals_t; - -typedef struct ace_list { - acevals_t user_obj; - avl_tree_t user; - int numusers; - acevals_t group_obj; - avl_tree_t group; - int numgroups; - acevals_t other_obj; - uint32_t acl_mask; - int hasmask; - int dfacl_flag; - ace_to_aent_state_t state; - int seen; /* bitmask of all aclent_t a_type values seen */ -} ace_list_t; typedef union { const char *file; int fd; } acl_inp; -acl_t * -acl_alloc(enum acl_type type) -{ - acl_t *aclp; - - aclp = malloc(sizeof (acl_t)); - - if (aclp == NULL) - return (NULL); - - aclp->acl_aclp = NULL; - aclp->acl_cnt = 0; - - switch (type) { - case ACE_T: - aclp->acl_type = ACE_T; - aclp->acl_entry_size = sizeof (ace_t); - break; - case ACLENT_T: - aclp->acl_type = ACLENT_T; - aclp->acl_entry_size = sizeof (aclent_t); - break; - default: - acl_free(aclp); - aclp = NULL; - } - return (aclp); -} - -/* - * Free acl_t structure - */ -void -acl_free(acl_t *aclp) -{ - if (aclp == NULL) - return; - - if (aclp->acl_aclp) - free(aclp->acl_aclp); - free(aclp); -} /* * Determine whether a file has a trivial ACL @@ -242,1194 +94,6 @@ acl_trivial(const char *filename) return (val); } -static uint32_t -access_mask_set(int haswriteperm, int hasreadperm, int isowner, int isallow) -{ - uint32_t access_mask = 0; - int acl_produce; - int synchronize_set = 0, write_owner_set = 0; - int delete_set = 0, write_attrs_set = 0; - int read_named_set = 0, write_named_set = 0; - - acl_produce = (ACL_SYNCHRONIZE_SET_ALLOW | - ACL_WRITE_ATTRS_OWNER_SET_ALLOW | - ACL_WRITE_ATTRS_WRITER_SET_DENY); - - if (isallow) { - synchronize_set = ACL_SYNCHRONIZE_SET_ALLOW; - write_owner_set = ACL_WRITE_OWNER_SET_ALLOW; - delete_set = ACL_DELETE_SET_ALLOW; - if (hasreadperm) - read_named_set = ACL_READ_NAMED_READER_SET_ALLOW; - if (haswriteperm) - write_named_set = ACL_WRITE_NAMED_WRITER_SET_ALLOW; - if (isowner) - write_attrs_set = ACL_WRITE_ATTRS_OWNER_SET_ALLOW; - else if (haswriteperm) - write_attrs_set = ACL_WRITE_ATTRS_WRITER_SET_ALLOW; - } else { - - synchronize_set = ACL_SYNCHRONIZE_SET_DENY; - write_owner_set = ACL_WRITE_OWNER_SET_DENY; - delete_set = ACL_DELETE_SET_DENY; - if (hasreadperm) - read_named_set = ACL_READ_NAMED_READER_SET_DENY; - if (haswriteperm) - write_named_set = ACL_WRITE_NAMED_WRITER_SET_DENY; - if (isowner) - write_attrs_set = ACL_WRITE_ATTRS_OWNER_SET_DENY; - else if (haswriteperm) - write_attrs_set = ACL_WRITE_ATTRS_WRITER_SET_DENY; - else - /* - * If the entity is not the owner and does not - * have write permissions ACE_WRITE_ATTRIBUTES will - * always go in the DENY ACE. - */ - access_mask |= ACE_WRITE_ATTRIBUTES; - } - - if (acl_produce & synchronize_set) - access_mask |= ACE_SYNCHRONIZE; - if (acl_produce & write_owner_set) - access_mask |= ACE_WRITE_OWNER; - if (acl_produce & delete_set) - access_mask |= ACE_DELETE; - if (acl_produce & write_attrs_set) - access_mask |= ACE_WRITE_ATTRIBUTES; - if (acl_produce & read_named_set) - access_mask |= ACE_READ_NAMED_ATTRS; - if (acl_produce & write_named_set) - access_mask |= ACE_WRITE_NAMED_ATTRS; - - return (access_mask); -} - -/* - * Given an mode_t, convert it into an access_mask as used - * by nfsace, assuming aclent_t -> nfsace semantics. - */ -static uint32_t -mode_to_ace_access(mode_t mode, int isdir, int isowner, int isallow) -{ - uint32_t access = 0; - int haswriteperm = 0; - int hasreadperm = 0; - - if (isallow) { - haswriteperm = (mode & 02); - hasreadperm = (mode & 04); - } else { - haswriteperm = !(mode & 02); - hasreadperm = !(mode & 04); - } - - /* - * The following call takes care of correctly setting the following - * mask bits in the access_mask: - * ACE_SYNCHRONIZE, ACE_WRITE_OWNER, ACE_DELETE, - * ACE_WRITE_ATTRIBUTES, ACE_WRITE_NAMED_ATTRS, ACE_READ_NAMED_ATTRS - */ - access = access_mask_set(haswriteperm, hasreadperm, isowner, isallow); - - if (isallow) { - access |= ACE_READ_ACL | ACE_READ_ATTRIBUTES; - if (isowner) - access |= ACE_WRITE_ACL; - } else { - if (! isowner) - access |= ACE_WRITE_ACL; - } - - /* read */ - if (mode & 04) { - access |= ACE_READ_DATA; - } - /* write */ - if (mode & 02) { - access |= ACE_WRITE_DATA | - ACE_APPEND_DATA; - if (isdir) - access |= ACE_DELETE_CHILD; - } - /* exec */ - if (mode & 01) { - access |= ACE_EXECUTE; - } - - return (access); -} - -/* - * Given an nfsace (presumably an ALLOW entry), make a - * corresponding DENY entry at the address given. - */ -static void -ace_make_deny(ace_t *allow, ace_t *deny, int isdir, int isowner) -{ - (void) memcpy(deny, allow, sizeof (ace_t)); - - deny->a_who = allow->a_who; - - deny->a_type = ACE_ACCESS_DENIED_ACE_TYPE; - deny->a_access_mask ^= ACE_POSIX_SUPPORTED_BITS; - if (isdir) - deny->a_access_mask ^= ACE_DELETE_CHILD; - - deny->a_access_mask &= ~(ACE_SYNCHRONIZE | ACE_WRITE_OWNER | - ACE_DELETE | ACE_WRITE_ATTRIBUTES | ACE_READ_NAMED_ATTRS | - ACE_WRITE_NAMED_ATTRS); - deny->a_access_mask |= access_mask_set((allow->a_access_mask & - ACE_WRITE_DATA), (allow->a_access_mask & ACE_READ_DATA), isowner, - B_FALSE); -} -/* - * Make an initial pass over an array of aclent_t's. Gather - * information such as an ACL_MASK (if any), number of users, - * number of groups, and whether the array needs to be sorted. - */ -static int -ln_aent_preprocess(aclent_t *aclent, int n, - int *hasmask, mode_t *mask, - int *numuser, int *numgroup, int *needsort) -{ - int error = 0; - int i; - int curtype = 0; - - *hasmask = 0; - *mask = 07; - *needsort = 0; - *numuser = 0; - *numgroup = 0; - - for (i = 0; i < n; i++) { - if (aclent[i].a_type < curtype) - *needsort = 1; - else if (aclent[i].a_type > curtype) - curtype = aclent[i].a_type; - if (aclent[i].a_type & USER) - (*numuser)++; - if (aclent[i].a_type & (GROUP | GROUP_OBJ)) - (*numgroup)++; - if (aclent[i].a_type & CLASS_OBJ) { - if (*hasmask) { - error = EINVAL; - goto out; - } else { - *hasmask = 1; - *mask = aclent[i].a_perm; - } - } - } - - if ((! *hasmask) && (*numuser + *numgroup > 1)) { - error = EINVAL; - goto out; - } - -out: - return (error); -} - -/* - * Convert an array of aclent_t into an array of nfsace entries, - * following POSIX draft -> nfsv4 conversion semantics as outlined in - * the IETF draft. - */ -static int -ln_aent_to_ace(aclent_t *aclent, int n, ace_t **acepp, int *rescount, int isdir) -{ - int error = 0; - mode_t mask; - int numuser, numgroup, needsort; - int resultsize = 0; - int i, groupi = 0, skip; - ace_t *acep, *result = NULL; - int hasmask; - - error = ln_aent_preprocess(aclent, n, &hasmask, &mask, - &numuser, &numgroup, &needsort); - if (error != 0) - goto out; - - /* allow + deny for each aclent */ - resultsize = n * 2; - if (hasmask) { - /* - * stick extra deny on the group_obj and on each - * user|group for the mask (the group_obj was added - * into the count for numgroup) - */ - resultsize += numuser + numgroup; - /* ... and don't count the mask itself */ - resultsize -= 2; - } - - /* sort the source if necessary */ - if (needsort) - ksort((caddr_t)aclent, n, sizeof (aclent_t), cmp2acls); - - result = acep = calloc(1, resultsize * sizeof (ace_t)); - if (result == NULL) - goto out; - - for (i = 0; i < n; i++) { - /* - * don't process CLASS_OBJ (mask); mask was grabbed in - * ln_aent_preprocess() - */ - if (aclent[i].a_type & CLASS_OBJ) - continue; - - /* If we need an ACL_MASK emulator, prepend it now */ - if ((hasmask) && - (aclent[i].a_type & (USER | GROUP | GROUP_OBJ))) { - acep->a_type = ACE_ACCESS_DENIED_ACE_TYPE; - acep->a_flags = 0; - if (aclent[i].a_type & GROUP_OBJ) { - acep->a_who = (uid_t)-1; - acep->a_flags |= - (ACE_IDENTIFIER_GROUP|ACE_GROUP); - } else if (aclent[i].a_type & USER) { - acep->a_who = aclent[i].a_id; - } else { - acep->a_who = aclent[i].a_id; - acep->a_flags |= ACE_IDENTIFIER_GROUP; - } - if (aclent[i].a_type & ACL_DEFAULT) { - acep->a_flags |= ACE_INHERIT_ONLY_ACE | - ACE_FILE_INHERIT_ACE | - ACE_DIRECTORY_INHERIT_ACE; - } - /* - * Set the access mask for the prepended deny - * ace. To do this, we invert the mask (found - * in ln_aent_preprocess()) then convert it to an - * DENY ace access_mask. - */ - acep->a_access_mask = mode_to_ace_access((mask ^ 07), - isdir, 0, 0); - acep += 1; - } - - /* handle a_perm -> access_mask */ - acep->a_access_mask = mode_to_ace_access(aclent[i].a_perm, - isdir, aclent[i].a_type & USER_OBJ, 1); - - /* emulate a default aclent */ - if (aclent[i].a_type & ACL_DEFAULT) { - acep->a_flags |= ACE_INHERIT_ONLY_ACE | - ACE_FILE_INHERIT_ACE | - ACE_DIRECTORY_INHERIT_ACE; - } - - /* - * handle a_perm and a_id - * - * this must be done last, since it involves the - * corresponding deny aces, which are handled - * differently for each different a_type. - */ - if (aclent[i].a_type & USER_OBJ) { - acep->a_who = (uid_t)-1; - acep->a_flags |= ACE_OWNER; - ace_make_deny(acep, acep + 1, isdir, B_TRUE); - acep += 2; - } else if (aclent[i].a_type & USER) { - acep->a_who = aclent[i].a_id; - ace_make_deny(acep, acep + 1, isdir, B_FALSE); - acep += 2; - } else if (aclent[i].a_type & (GROUP_OBJ | GROUP)) { - if (aclent[i].a_type & GROUP_OBJ) { - acep->a_who = (uid_t)-1; - acep->a_flags |= ACE_GROUP; - } else { - acep->a_who = aclent[i].a_id; - } - acep->a_flags |= ACE_IDENTIFIER_GROUP; - /* - * Set the corresponding deny for the group ace. - * - * The deny aces go after all of the groups, unlike - * everything else, where they immediately follow - * the allow ace. - * - * We calculate "skip", the number of slots to - * skip ahead for the deny ace, here. - * - * The pattern is: - * MD1 A1 MD2 A2 MD3 A3 D1 D2 D3 - * thus, skip is - * (2 * numgroup) - 1 - groupi - * (2 * numgroup) to account for MD + A - * - 1 to account for the fact that we're on the - * access (A), not the mask (MD) - * - groupi to account for the fact that we have - * passed up groupi number of MD's. - */ - skip = (2 * numgroup) - 1 - groupi; - ace_make_deny(acep, acep + skip, isdir, B_FALSE); - /* - * If we just did the last group, skip acep past - * all of the denies; else, just move ahead one. - */ - if (++groupi >= numgroup) - acep += numgroup + 1; - else - acep += 1; - } else if (aclent[i].a_type & OTHER_OBJ) { - acep->a_who = (uid_t)-1; - acep->a_flags |= ACE_EVERYONE; - ace_make_deny(acep, acep + 1, isdir, B_FALSE); - acep += 2; - } else { - error = EINVAL; - goto out; - } - } - - *acepp = result; - *rescount = resultsize; - -out: - if (error != 0) { - if ((result != NULL) && (resultsize > 0)) { - free(result); - } - } - - return (error); -} - -static int -convert_aent_to_ace(aclent_t *aclentp, int aclcnt, int isdir, - ace_t **retacep, int *retacecnt) -{ - ace_t *acep; - ace_t *dfacep; - int acecnt = 0; - int dfacecnt = 0; - int dfaclstart = 0; - int dfaclcnt = 0; - aclent_t *aclp; - int i; - int error; - - ksort((caddr_t)aclentp, aclcnt, sizeof (aclent_t), cmp2acls); - - for (i = 0, aclp = aclentp; i < aclcnt; aclp++, i++) { - if (aclp->a_type & ACL_DEFAULT) - break; - } - - if (i < aclcnt) { - dfaclstart = i; - dfaclcnt = aclcnt - i; - } - - if (dfaclcnt && isdir == 0) { - return (-1); - } - - error = ln_aent_to_ace(aclentp, i, &acep, &acecnt, isdir); - if (error) - return (-1); - - if (dfaclcnt) { - error = ln_aent_to_ace(&aclentp[dfaclstart], dfaclcnt, - &dfacep, &dfacecnt, isdir); - if (error) { - if (acep) { - free(acep); - } - return (-1); - } - } - - if (dfacecnt != 0) { - acep = realloc(acep, sizeof (ace_t) * (acecnt + dfacecnt)); - if (acep == NULL) - return (-1); - if (dfaclcnt) { - (void) memcpy(acep + acecnt, dfacep, - sizeof (ace_t) * dfacecnt); - } - } - if (dfaclcnt) - free(dfacep); - - *retacecnt = acecnt + dfacecnt; - *retacep = acep; - return (0); -} - -static void -acevals_init(acevals_t *vals, uid_t key) -{ - bzero(vals, sizeof (*vals)); - vals->allowed = ACE_MASK_UNDEFINED; - vals->denied = ACE_MASK_UNDEFINED; - vals->mask = ACE_MASK_UNDEFINED; - vals->key = key; -} - -static void -ace_list_init(ace_list_t *al, int dfacl_flag) -{ - acevals_init(&al->user_obj, NULL); - acevals_init(&al->group_obj, NULL); - acevals_init(&al->other_obj, NULL); - al->numusers = 0; - al->numgroups = 0; - al->acl_mask = 0; - al->hasmask = 0; - al->state = ace_unused; - al->seen = 0; - al->dfacl_flag = dfacl_flag; -} - -/* - * Find or create an acevals holder for a given id and avl tree. - * - * Note that only one thread will ever touch these avl trees, so - * there is no need for locking. - */ -static acevals_t * -acevals_find(ace_t *ace, avl_tree_t *avl, int *num) -{ - acevals_t key, *rc; - avl_index_t where; - - key.key = ace->a_who; - rc = avl_find(avl, &key, &where); - if (rc != NULL) - return (rc); - - /* this memory is freed by ln_ace_to_aent()->ace_list_free() */ - rc = calloc(1, sizeof (acevals_t)); - if (rc == NULL) - return (rc); - acevals_init(rc, ace->a_who); - avl_insert(avl, rc, where); - (*num)++; - - return (rc); -} - -static int -access_mask_check(ace_t *acep, int mask_bit, int isowner) -{ - int set_deny, err_deny; - int set_allow, err_allow; - int acl_consume; - int haswriteperm, hasreadperm; - - if (acep->a_type == ACE_ACCESS_DENIED_ACE_TYPE) { - haswriteperm = (acep->a_access_mask & ACE_WRITE_DATA) ? 0 : 1; - hasreadperm = (acep->a_access_mask & ACE_READ_DATA) ? 0 : 1; - } else { - haswriteperm = (acep->a_access_mask & ACE_WRITE_DATA) ? 1 : 0; - hasreadperm = (acep->a_access_mask & ACE_READ_DATA) ? 1 : 0; - } - - acl_consume = (ACL_SYNCHRONIZE_ERR_DENY | - ACL_DELETE_ERR_DENY | - ACL_WRITE_OWNER_ERR_DENY | - ACL_WRITE_OWNER_ERR_ALLOW | - ACL_WRITE_ATTRS_OWNER_SET_ALLOW | - ACL_WRITE_ATTRS_OWNER_ERR_DENY | - ACL_WRITE_ATTRS_WRITER_SET_DENY | - ACL_WRITE_ATTRS_WRITER_ERR_ALLOW | - ACL_WRITE_NAMED_WRITER_ERR_DENY | - ACL_READ_NAMED_READER_ERR_DENY); - - if (mask_bit == ACE_SYNCHRONIZE) { - set_deny = ACL_SYNCHRONIZE_SET_DENY; - err_deny = ACL_SYNCHRONIZE_ERR_DENY; - set_allow = ACL_SYNCHRONIZE_SET_ALLOW; - err_allow = ACL_SYNCHRONIZE_ERR_ALLOW; - } else if (mask_bit == ACE_WRITE_OWNER) { - set_deny = ACL_WRITE_OWNER_SET_DENY; - err_deny = ACL_WRITE_OWNER_ERR_DENY; - set_allow = ACL_WRITE_OWNER_SET_ALLOW; - err_allow = ACL_WRITE_OWNER_ERR_ALLOW; - } else if (mask_bit == ACE_DELETE) { - set_deny = ACL_DELETE_SET_DENY; - err_deny = ACL_DELETE_ERR_DENY; - set_allow = ACL_DELETE_SET_ALLOW; - err_allow = ACL_DELETE_ERR_ALLOW; - } else if (mask_bit == ACE_WRITE_ATTRIBUTES) { - if (isowner) { - set_deny = ACL_WRITE_ATTRS_OWNER_SET_DENY; - err_deny = ACL_WRITE_ATTRS_OWNER_ERR_DENY; - set_allow = ACL_WRITE_ATTRS_OWNER_SET_ALLOW; - err_allow = ACL_WRITE_ATTRS_OWNER_ERR_ALLOW; - } else if (haswriteperm) { - set_deny = ACL_WRITE_ATTRS_WRITER_SET_DENY; - err_deny = ACL_WRITE_ATTRS_WRITER_ERR_DENY; - set_allow = ACL_WRITE_ATTRS_WRITER_SET_ALLOW; - err_allow = ACL_WRITE_ATTRS_WRITER_ERR_ALLOW; - } else { - if ((acep->a_access_mask & mask_bit) && - (acep->a_type & ACE_ACCESS_ALLOWED_ACE_TYPE)) { - return (ENOTSUP); - } - return (0); - } - } else if (mask_bit == ACE_READ_NAMED_ATTRS) { - if (!hasreadperm) - return (0); - - set_deny = ACL_READ_NAMED_READER_SET_DENY; - err_deny = ACL_READ_NAMED_READER_ERR_DENY; - set_allow = ACL_READ_NAMED_READER_SET_ALLOW; - err_allow = ACL_READ_NAMED_READER_ERR_ALLOW; - } else if (mask_bit == ACE_WRITE_NAMED_ATTRS) { - if (!haswriteperm) - return (0); - - set_deny = ACL_WRITE_NAMED_WRITER_SET_DENY; - err_deny = ACL_WRITE_NAMED_WRITER_ERR_DENY; - set_allow = ACL_WRITE_NAMED_WRITER_SET_ALLOW; - err_allow = ACL_WRITE_NAMED_WRITER_ERR_ALLOW; - } else { - return (EINVAL); - } - - if (acep->a_type == ACE_ACCESS_DENIED_ACE_TYPE) { - if (acl_consume & set_deny) { - if (!(acep->a_access_mask & mask_bit)) { - return (ENOTSUP); - } - } else if (acl_consume & err_deny) { - if (acep->a_access_mask & mask_bit) { - return (ENOTSUP); - } - } - } else { - /* ACE_ACCESS_ALLOWED_ACE_TYPE */ - if (acl_consume & set_allow) { - if (!(acep->a_access_mask & mask_bit)) { - return (ENOTSUP); - } - } else if (acl_consume & err_allow) { - if (acep->a_access_mask & mask_bit) { - return (ENOTSUP); - } - } - } - return (0); -} - -static int -ace_to_aent_legal(ace_t *acep) -{ - int error = 0; - int isowner; - - /* only ALLOW or DENY */ - if ((acep->a_type != ACE_ACCESS_ALLOWED_ACE_TYPE) && - (acep->a_type != ACE_ACCESS_DENIED_ACE_TYPE)) { - error = ENOTSUP; - goto out; - } - - /* check for invalid flags */ - if (acep->a_flags & ~(ACE_VALID_FLAG_BITS)) { - error = EINVAL; - goto out; - } - - /* some flags are illegal */ - if (acep->a_flags & (ACE_SUCCESSFUL_ACCESS_ACE_FLAG | - ACE_FAILED_ACCESS_ACE_FLAG | - ACE_NO_PROPAGATE_INHERIT_ACE)) { - error = ENOTSUP; - goto out; - } - - /* check for invalid masks */ - if (acep->a_access_mask & ~(ACE_VALID_MASK_BITS)) { - error = EINVAL; - goto out; - } - - if ((acep->a_flags & ACE_OWNER)) { - isowner = 1; - } else { - isowner = 0; - } - - error = access_mask_check(acep, ACE_SYNCHRONIZE, isowner); - if (error) - goto out; - - error = access_mask_check(acep, ACE_WRITE_OWNER, isowner); - if (error) - goto out; - - error = access_mask_check(acep, ACE_DELETE, isowner); - if (error) - goto out; - - error = access_mask_check(acep, ACE_WRITE_ATTRIBUTES, isowner); - if (error) - goto out; - - error = access_mask_check(acep, ACE_READ_NAMED_ATTRS, isowner); - if (error) - goto out; - - error = access_mask_check(acep, ACE_WRITE_NAMED_ATTRS, isowner); - if (error) - goto out; - - /* more detailed checking of masks */ - if (acep->a_type == ACE_ACCESS_ALLOWED_ACE_TYPE) { - if (! (acep->a_access_mask & ACE_READ_ATTRIBUTES)) { - error = ENOTSUP; - goto out; - } - if ((acep->a_access_mask & ACE_WRITE_DATA) && - (! (acep->a_access_mask & ACE_APPEND_DATA))) { - error = ENOTSUP; - goto out; - } - if ((! (acep->a_access_mask & ACE_WRITE_DATA)) && - (acep->a_access_mask & ACE_APPEND_DATA)) { - error = ENOTSUP; - goto out; - } - } - - /* ACL enforcement */ - if ((acep->a_access_mask & ACE_READ_ACL) && - (acep->a_type != ACE_ACCESS_ALLOWED_ACE_TYPE)) { - error = ENOTSUP; - goto out; - } - if (acep->a_access_mask & ACE_WRITE_ACL) { - if ((acep->a_type == ACE_ACCESS_DENIED_ACE_TYPE) && - (isowner)) { - error = ENOTSUP; - goto out; - } - if ((acep->a_type == ACE_ACCESS_ALLOWED_ACE_TYPE) && - (! isowner)) { - error = ENOTSUP; - goto out; - } - } - -out: - return (error); -} - -static int -ace_mask_to_mode(uint32_t mask, o_mode_t *modep, int isdir) -{ - int error = 0; - o_mode_t mode = 0; - uint32_t bits, wantbits; - - /* read */ - if (mask & ACE_READ_DATA) - mode |= 04; - - /* write */ - wantbits = (ACE_WRITE_DATA | ACE_APPEND_DATA); - if (isdir) - wantbits |= ACE_DELETE_CHILD; - bits = mask & wantbits; - if (bits != 0) { - if (bits != wantbits) { - error = ENOTSUP; - goto out; - } - mode |= 02; - } - - /* exec */ - if (mask & ACE_EXECUTE) { - mode |= 01; - } - - *modep = mode; - -out: - return (error); -} - -static int -ace_allow_to_mode(uint32_t mask, o_mode_t *modep, int isdir) -{ - /* ACE_READ_ACL and ACE_READ_ATTRIBUTES must both be set */ - if ((mask & (ACE_READ_ACL | ACE_READ_ATTRIBUTES)) != - (ACE_READ_ACL | ACE_READ_ATTRIBUTES)) { - return (ENOTSUP); - } - - return (ace_mask_to_mode(mask, modep, isdir)); -} - -static int -acevals_to_aent(acevals_t *vals, aclent_t *dest, ace_list_t *list, - uid_t owner, gid_t group, int isdir) -{ - int error; - uint32_t flips = ACE_POSIX_SUPPORTED_BITS; - - if (isdir) - flips |= ACE_DELETE_CHILD; - if (vals->allowed != (vals->denied ^ flips)) { - error = ENOTSUP; - goto out; - } - if ((list->hasmask) && (list->acl_mask != vals->mask) && - (vals->aent_type & (USER | GROUP | GROUP_OBJ))) { - error = ENOTSUP; - goto out; - } - error = ace_allow_to_mode(vals->allowed, &dest->a_perm, isdir); - if (error != 0) - goto out; - dest->a_type = vals->aent_type; - if (dest->a_type & (USER | GROUP)) { - dest->a_id = vals->key; - } else if (dest->a_type & USER_OBJ) { - dest->a_id = owner; - } else if (dest->a_type & GROUP_OBJ) { - dest->a_id = group; - } else if (dest->a_type & OTHER_OBJ) { - dest->a_id = 0; - } else { - error = EINVAL; - goto out; - } - -out: - return (error); -} - -static int -ace_list_to_aent(ace_list_t *list, aclent_t **aclentp, int *aclcnt, - uid_t owner, gid_t group, int isdir) -{ - int error = 0; - aclent_t *aent, *result = NULL; - acevals_t *vals; - int resultcount; - - if ((list->seen & (USER_OBJ | GROUP_OBJ | OTHER_OBJ)) != - (USER_OBJ | GROUP_OBJ | OTHER_OBJ)) { - error = ENOTSUP; - goto out; - } - if ((! list->hasmask) && (list->numusers + list->numgroups > 0)) { - error = ENOTSUP; - goto out; - } - - resultcount = 3 + list->numusers + list->numgroups; - /* - * This must be the same condition as below, when we add the CLASS_OBJ - * (aka ACL mask) - */ - if ((list->hasmask) || (! list->dfacl_flag)) - resultcount += 1; - - result = aent = calloc(1, resultcount * sizeof (aclent_t)); - - if (result == NULL) { - error = ENOMEM; - goto out; - } - - /* USER_OBJ */ - if (!(list->user_obj.aent_type & USER_OBJ)) { - error = EINVAL; - goto out; - } - - error = acevals_to_aent(&list->user_obj, aent, list, owner, group, - isdir); - - if (error != 0) - goto out; - ++aent; - /* USER */ - vals = NULL; - for (vals = avl_first(&list->user); vals != NULL; - vals = AVL_NEXT(&list->user, vals)) { - if (!(vals->aent_type & USER)) { - error = EINVAL; - goto out; - } - error = acevals_to_aent(vals, aent, list, owner, group, - isdir); - if (error != 0) - goto out; - ++aent; - } - /* GROUP_OBJ */ - if (!(list->group_obj.aent_type & GROUP_OBJ)) { - error = EINVAL; - goto out; - } - error = acevals_to_aent(&list->group_obj, aent, list, owner, group, - isdir); - if (error != 0) - goto out; - ++aent; - /* GROUP */ - vals = NULL; - for (vals = avl_first(&list->group); vals != NULL; - vals = AVL_NEXT(&list->group, vals)) { - if (!(vals->aent_type & GROUP)) { - error = EINVAL; - goto out; - } - error = acevals_to_aent(vals, aent, list, owner, group, - isdir); - if (error != 0) - goto out; - ++aent; - } - /* - * CLASS_OBJ (aka ACL_MASK) - * - * An ACL_MASK is not fabricated if the ACL is a default ACL. - * This is to follow UFS's behavior. - */ - if ((list->hasmask) || (! list->dfacl_flag)) { - if (list->hasmask) { - uint32_t flips = ACE_POSIX_SUPPORTED_BITS; - if (isdir) - flips |= ACE_DELETE_CHILD; - error = ace_mask_to_mode(list->acl_mask ^ flips, - &aent->a_perm, isdir); - if (error != 0) - goto out; - } else { - /* fabricate the ACL_MASK from the group permissions */ - error = ace_mask_to_mode(list->group_obj.allowed, - &aent->a_perm, isdir); - if (error != 0) - goto out; - } - aent->a_id = 0; - aent->a_type = CLASS_OBJ | list->dfacl_flag; - ++aent; - } - /* OTHER_OBJ */ - if (!(list->other_obj.aent_type & OTHER_OBJ)) { - error = EINVAL; - goto out; - } - error = acevals_to_aent(&list->other_obj, aent, list, owner, group, - isdir); - if (error != 0) - goto out; - ++aent; - - *aclentp = result; - *aclcnt = resultcount; - -out: - if (error != 0) { - if (result != NULL) - free(result); - } - - return (error); -} - -/* - * free all data associated with an ace_list - */ -static void -ace_list_free(ace_list_t *al) -{ - acevals_t *node; - void *cookie; - - if (al == NULL) - return; - - cookie = NULL; - while ((node = avl_destroy_nodes(&al->user, &cookie)) != NULL) - free(node); - cookie = NULL; - while ((node = avl_destroy_nodes(&al->group, &cookie)) != NULL) - free(node); - - avl_destroy(&al->user); - avl_destroy(&al->group); - - /* free the container itself */ - free(al); -} - -static int -acevals_compare(const void *va, const void *vb) -{ - const acevals_t *a = va, *b = vb; - - if (a->key == b->key) - return (0); - - if (a->key > b->key) - return (1); - - else - return (-1); -} - -/* - * Convert a list of ace_t entries to equivalent regular and default - * aclent_t lists. Return error (ENOTSUP) when conversion is not possible. - */ -static int -ln_ace_to_aent(ace_t *ace, int n, uid_t owner, gid_t group, - aclent_t **aclentp, int *aclcnt, aclent_t **dfaclentp, int *dfaclcnt, - int isdir) -{ - int error = 0; - ace_t *acep; - uint32_t bits; - int i; - ace_list_t *normacl = NULL, *dfacl = NULL, *acl; - acevals_t *vals; - - *aclentp = NULL; - *aclcnt = 0; - *dfaclentp = NULL; - *dfaclcnt = 0; - - /* we need at least user_obj, group_obj, and other_obj */ - if (n < 6) { - error = ENOTSUP; - goto out; - } - if (ace == NULL) { - error = EINVAL; - goto out; - } - - normacl = calloc(1, sizeof (ace_list_t)); - - if (normacl == NULL) { - error = errno; - goto out; - } - - avl_create(&normacl->user, acevals_compare, sizeof (acevals_t), - offsetof(acevals_t, avl)); - avl_create(&normacl->group, acevals_compare, sizeof (acevals_t), - offsetof(acevals_t, avl)); - - ace_list_init(normacl, 0); - - dfacl = calloc(1, sizeof (ace_list_t)); - if (dfacl == NULL) { - error = errno; - goto out; - } - avl_create(&dfacl->user, acevals_compare, sizeof (acevals_t), - offsetof(acevals_t, avl)); - avl_create(&dfacl->group, acevals_compare, sizeof (acevals_t), - offsetof(acevals_t, avl)); - ace_list_init(dfacl, ACL_DEFAULT); - - /* process every ace_t... */ - for (i = 0; i < n; i++) { - acep = &ace[i]; - - /* rule out certain cases quickly */ - error = ace_to_aent_legal(acep); - if (error != 0) - goto out; - - /* - * Turn off these bits in order to not have to worry about - * them when doing the checks for compliments. - */ - acep->a_access_mask &= ~(ACE_WRITE_OWNER | ACE_DELETE | - ACE_SYNCHRONIZE | ACE_WRITE_ATTRIBUTES | - ACE_READ_NAMED_ATTRS | ACE_WRITE_NAMED_ATTRS); - - /* see if this should be a regular or default acl */ - bits = acep->a_flags & - (ACE_INHERIT_ONLY_ACE | - ACE_FILE_INHERIT_ACE | - ACE_DIRECTORY_INHERIT_ACE); - if (bits != 0) { - /* all or nothing on these inherit bits */ - if (bits != (ACE_INHERIT_ONLY_ACE | - ACE_FILE_INHERIT_ACE | - ACE_DIRECTORY_INHERIT_ACE)) { - error = ENOTSUP; - goto out; - } - acl = dfacl; - } else { - acl = normacl; - } - - if ((acep->a_flags & ACE_OWNER)) { - if (acl->state > ace_user_obj) { - error = ENOTSUP; - goto out; - } - acl->state = ace_user_obj; - acl->seen |= USER_OBJ; - vals = &acl->user_obj; - vals->aent_type = USER_OBJ | acl->dfacl_flag; - } else if ((acep->a_flags & ACE_EVERYONE)) { - acl->state = ace_other_obj; - acl->seen |= OTHER_OBJ; - vals = &acl->other_obj; - vals->aent_type = OTHER_OBJ | acl->dfacl_flag; - } else if (acep->a_flags & ACE_IDENTIFIER_GROUP) { - if (acl->state > ace_group) { - error = ENOTSUP; - goto out; - } - if ((acep->a_flags & ACE_GROUP)) { - acl->seen |= GROUP_OBJ; - vals = &acl->group_obj; - vals->aent_type = GROUP_OBJ | acl->dfacl_flag; - } else { - acl->seen |= GROUP; - vals = acevals_find(acep, &acl->group, - &acl->numgroups); - if (vals == NULL) { - error = ENOMEM; - goto out; - } - vals->aent_type = GROUP | acl->dfacl_flag; - } - acl->state = ace_group; - } else { - if (acl->state > ace_user) { - error = ENOTSUP; - goto out; - } - acl->state = ace_user; - acl->seen |= USER; - vals = acevals_find(acep, &acl->user, - &acl->numusers); - if (vals == NULL) { - error = ENOMEM; - goto out; - } - vals->aent_type = USER | acl->dfacl_flag; - } - - if (!(acl->state > ace_unused)) { - error = EINVAL; - goto out; - } - - if (acep->a_type == ACE_ACCESS_ALLOWED_ACE_TYPE) { - /* no more than one allowed per aclent_t */ - if (vals->allowed != ACE_MASK_UNDEFINED) { - error = ENOTSUP; - goto out; - } - vals->allowed = acep->a_access_mask; - } else { - /* - * it's a DENY; if there was a previous DENY, it - * must have been an ACL_MASK. - */ - if (vals->denied != ACE_MASK_UNDEFINED) { - /* ACL_MASK is for USER and GROUP only */ - if ((acl->state != ace_user) && - (acl->state != ace_group)) { - error = ENOTSUP; - goto out; - } - - if (! acl->hasmask) { - acl->hasmask = 1; - acl->acl_mask = vals->denied; - /* check for mismatched ACL_MASK emulations */ - } else if (acl->acl_mask != vals->denied) { - error = ENOTSUP; - goto out; - } - vals->mask = vals->denied; - } - vals->denied = acep->a_access_mask; - } - } - - /* done collating; produce the aclent_t lists */ - if (normacl->state != ace_unused) { - error = ace_list_to_aent(normacl, aclentp, aclcnt, - owner, group, isdir); - if (error != 0) { - goto out; - } - } - if (dfacl->state != ace_unused) { - error = ace_list_to_aent(dfacl, dfaclentp, dfaclcnt, - owner, group, isdir); - if (error != 0) { - goto out; - } - } - -out: - if (normacl != NULL) - ace_list_free(normacl); - if (dfacl != NULL) - ace_list_free(dfacl); - - return (error); -} - -static int -convert_ace_to_aent(ace_t *acebufp, int acecnt, int isdir, - uid_t owner, gid_t group, aclent_t **retaclentp, int *retaclcnt) -{ - int error; - aclent_t *aclentp, *dfaclentp; - int aclcnt, dfaclcnt; - - error = ln_ace_to_aent(acebufp, acecnt, owner, group, - &aclentp, &aclcnt, &dfaclentp, &dfaclcnt, isdir); - - if (error) - return (error); - - - if (dfaclcnt != 0) { - /* - * Slap aclentp and dfaclentp into a single array. - */ - aclentp = realloc(aclentp, (sizeof (aclent_t) * aclcnt) + - (sizeof (aclent_t) * dfaclcnt)); - if (aclentp != NULL) { - (void) memcpy(aclentp + aclcnt, - dfaclentp, sizeof (aclent_t) * dfaclcnt); - } else { - error = -1; - } - } - - if (aclentp) { - *retaclentp = aclentp; - *retaclcnt = aclcnt + dfaclcnt; - } - - if (dfaclentp) - free(dfaclentp); - - return (error); -} static int cacl_get(acl_inp inp, int get_flag, int type, acl_t **aclp) @@ -1576,56 +240,6 @@ facl_get(int fd, int get_flag, acl_t **aclp) return (cacl_get(acl_inp, get_flag, ACL_FD, aclp)); } -static int -acl_translate(acl_t *aclp, int target_flavor, int isdir, uid_t owner, - gid_t group) -{ - int aclcnt; - void *acldata; - int error; - - /* - * See if we need to translate - */ - if ((target_flavor == _ACL_ACE_ENABLED && aclp->acl_type == ACE_T) || - (target_flavor == _ACL_ACLENT_ENABLED && - aclp->acl_type == ACLENT_T)) - return (0); - - if (target_flavor == -1) - return (-1); - - if (target_flavor == _ACL_ACE_ENABLED && - aclp->acl_type == ACLENT_T) { - error = convert_aent_to_ace(aclp->acl_aclp, - aclp->acl_cnt, isdir, (ace_t **)&acldata, &aclcnt); - if (error) { - errno = error; - return (-1); - } - } else if (target_flavor == _ACL_ACLENT_ENABLED && - aclp->acl_type == ACE_T) { - error = convert_ace_to_aent(aclp->acl_aclp, aclp->acl_cnt, - isdir, owner, group, (aclent_t **)&acldata, &aclcnt); - if (error) { - errno = error; - return (-1); - } - } else { - errno = ENOTSUP; - return (-1); - } - - /* - * replace old acl with newly translated acl - */ - free(aclp->acl_aclp); - aclp->acl_aclp = acldata; - aclp->acl_cnt = aclcnt; - aclp->acl_type = (target_flavor == _ACL_ACE_ENABLED) ? ACE_T : ACLENT_T; - return (0); -} - /* * Set an ACL, translates acl to ace_t when appropriate. */ diff --git a/usr/src/lib/libsec/common/aclutils.h b/usr/src/lib/libsec/common/aclutils.h index e5c34347bf..1db0aa4752 100644 --- a/usr/src/lib/libsec/common/aclutils.h +++ b/usr/src/lib/libsec/common/aclutils.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -29,6 +29,7 @@ #pragma ident "%Z%%M% %I% %E% SMI" #include <sys/types.h> +#include <sys/acl.h> #include <strings.h> #include <locale.h> #include <ctype.h> @@ -50,26 +51,6 @@ extern "C" { * append_data/add_subdirectory * when object of ACL is known. */ -#define ACL_IS_DIR 0x2 - -typedef enum acl_type { - ACLENT_T = 0, - ACE_T = 1 -} acl_type_t; - -/* - * acl flags - */ -#define ACL_IS_TRIVIAL 0x1 - -struct acl_info { - acl_type_t acl_type; /* style of acl */ - int acl_cnt; /* number of acl entries */ - int acl_entry_size; /* sizeof acl entry */ - int acl_flags; /* special flags about acl */ - void *acl_aclp; /* the acl */ -}; - #define PERM_TYPE_ACE 0x1 /* permissions are of ACE type */ #define PERM_TYPE_UNKNOWN 0x2 /* permission type not yet known */ @@ -105,7 +86,6 @@ extern void acl_error(const char *, ...); extern int acl_parse(const char *, acl_t **); extern int yyparse(void); extern void yyreset(void); -extern acl_t *acl_alloc(enum acl_type); extern acl_t *acl_to_aclp(enum acl_type, void *, int); #ifdef __cplusplus diff --git a/usr/src/lib/libsecdb/auth_attr.txt b/usr/src/lib/libsecdb/auth_attr.txt index afb8349e40..0b81cd20f0 100644 --- a/usr/src/lib/libsecdb/auth_attr.txt +++ b/usr/src/lib/libsecdb/auth_attr.txt @@ -121,6 +121,7 @@ solaris.smf.manage.mdns:::Manage Multicast DNS Service States::help=SmfMDNSState solaris.smf.manage.name-service-cache:::Manage Name Service Cache Daemon Service States::help=SmfNscdStates.html solaris.smf.manage.nwam:::Manage Network Auto-Magic Service States::help=SmfNWAMStates.html solaris.smf.manage.power:::Manage Power Management Service States::help=SmfPowerStates.html +solaris.smf.manage.smb:::Manage SMB Service States::help=SmfSMBStates.html solaris.smf.manage.rmvolmgr:::Manage Rmvolmgr Service States::help=SmfRmvolmgrStates.html solaris.smf.manage.routing:::Manage Routing Service States::help=SmfRoutingStates.html solaris.smf.manage.rpc.bind:::Manage RPC Program number mapper::help=SmfRPCBind.html @@ -137,6 +138,8 @@ solaris.smf.value.inetd:::Change values of SMF Inetd configuration paramaters::h solaris.smf.value.ipsec:::Change Values of SMF IPsec Properties::help=SmfValueIPsec.html solaris.smf.value.mdns:::Change Values of MDNS Service Properties::help=SmfValueMDNS.html solaris.smf.value.nwam:::Change Values of SMF Network Auto-Magic Properties::help=SmfValueNWAM.html +solaris.smf.value.smb:::Change Values of SMB Service Properties::help=SmfValueSMB.html +solaris.smf.read.smb:::Read permission for protected SMF SMB Service Properties::help=AuthReadSMB.html solaris.smf.value.routing:::Change Values of SMF Routing Properties::help=SmfValueRouting.html solaris.smf.value.tnd:::Change Trusted Network Daemon Service Property Values::help=ValueTND.html # diff --git a/usr/src/lib/libsecdb/help/auths/AuthReadSMB.html b/usr/src/lib/libsecdb/help/auths/AuthReadSMB.html new file mode 100755 index 0000000000..aec2ed97e8 --- /dev/null +++ b/usr/src/lib/libsecdb/help/auths/AuthReadSMB.html @@ -0,0 +1,38 @@ +<HTML> +<!-- + 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 2007 Sun Microsystems, Inc. All rights reserved. +-- Use is subject to license terms. +--> +<HEAD> + <TITLE> </TITLE> + + +</HEAD> +<BODY> +<!-- ident "%Z%%M% %I% %E% SMI" --> + +When View Rights is in the Authorizations Included column, it grants the authorization to list and read rights, in the Users tools of the Solaris Management Console. +<p> +If View Rights is grayed, then you are not entitled to Add or Remove this authorization. +<p> +</BODY> +</HTML> diff --git a/usr/src/lib/libsecdb/help/auths/Makefile b/usr/src/lib/libsecdb/help/auths/Makefile index 1bdd12e0ce..d28be1d4c5 100644 --- a/usr/src/lib/libsecdb/help/auths/Makefile +++ b/usr/src/lib/libsecdb/help/auths/Makefile @@ -93,6 +93,9 @@ HTMLENTS = \ SmfValueNADD.html \ SmfValueNWAM.html \ SmfValueRouting.html \ + SmfValueSMB.html \ + AuthReadSMB.html \ + SmfSMBStates.html \ SmfWpaStates.html \ NetworkHeader.html \ WifiConfig.html \ diff --git a/usr/src/lib/libsecdb/help/auths/SmfSMBStates.html b/usr/src/lib/libsecdb/help/auths/SmfSMBStates.html new file mode 100644 index 0000000000..baa85f002d --- /dev/null +++ b/usr/src/lib/libsecdb/help/auths/SmfSMBStates.html @@ -0,0 +1,40 @@ +<HTML> +<!-- + 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 2007 Sun Microsystems, Inc. All rights reserved. +Use is subject to license terms. +--> +<!-- SCCS keyword +#pragma ident "%Z%%M% %I% %E% SMI" +--> +<!-- + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> +--> +<BODY> +When Manage SMB Service States is in the Authorizations Include +column, it grants the authorization to enable, disable, or restart +the SMB service. +<p> +If Manage SMB Service States is grayed, then you are not entitled +to Add or Remove this authorization. +<BR> +</BODY> +</HTML> diff --git a/usr/src/lib/libsecdb/help/auths/SmfValueSMB.html b/usr/src/lib/libsecdb/help/auths/SmfValueSMB.html new file mode 100644 index 0000000000..0e5c3bc277 --- /dev/null +++ b/usr/src/lib/libsecdb/help/auths/SmfValueSMB.html @@ -0,0 +1,40 @@ +<HTML> +<!-- + 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 2007 Sun Microsystems, Inc. All rights reserved. +Use is subject to license terms. +--> +<!-- SCCS keyword +#pragma ident "%Z%%M% %I% %E% SMI" +--> +<!-- + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> +--> +<BODY> +When Value SMB Properties is in the Authorizations Include +column, it grants the authorization to change SMB service +property values. +<P> +If Value SMB Properties is grayed, then you are not entitled to +Add or Remove this authorization. +<BR> +</BODY> +</HTML> diff --git a/usr/src/lib/libsecdb/help/profiles/Makefile b/usr/src/lib/libsecdb/help/profiles/Makefile index 9ad6ede803..e0300d339b 100644 --- a/usr/src/lib/libsecdb/help/profiles/Makefile +++ b/usr/src/lib/libsecdb/help/profiles/Makefile @@ -62,6 +62,7 @@ HTMLENTS = \ RtPrntAdmin.html \ RtProcManagement.html \ RtRightsDelegate.html \ + RtSMBMngmnt.html \ RtSoftwareInstall.html \ RtSysEvMngmnt.html \ RtUserMngmnt.html \ diff --git a/usr/src/lib/libsecdb/help/profiles/RtSMBMngmnt.html b/usr/src/lib/libsecdb/help/profiles/RtSMBMngmnt.html new file mode 100644 index 0000000000..bbfef648ab --- /dev/null +++ b/usr/src/lib/libsecdb/help/profiles/RtSMBMngmnt.html @@ -0,0 +1,40 @@ +<HTML> +<!-- + 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 2007 Sun Microsystems, Inc. All rights reserved. +-- Use is subject to license terms. +--> +<HEAD> + <TITLE> </TITLE> + + +</HEAD> +<BODY> +<!-- ident "%Z%%M% %I% %E% SMI" --> + +When SMB Service Management is in the Rights Included column, it grants +the right to administer the SMB service. +<p> +If SMB Service Management is grayed, then you are not entitled to Add or +Remove this right. +<p> +</BODY> +</HTML> diff --git a/usr/src/lib/libsecdb/prof_attr.txt b/usr/src/lib/libsecdb/prof_attr.txt index 96107e01f4..1c171d427b 100644 --- a/usr/src/lib/libsecdb/prof_attr.txt +++ b/usr/src/lib/libsecdb/prof_attr.txt @@ -42,7 +42,7 @@ Log Management:::Manage log files:help=RtLogMngmnt.html Basic Solaris User:::Automatically assigned rights:auths=solaris.profmgr.read,solaris.jobs.user,solaris.mail.mailq,solaris.device.mount.removable;profiles=All;help=RtDefault.html Device Security:::Manage devices and Volume Manager:auths=solaris.device.*;help=RtDeviceSecurity.html DHCP Management:::Manage the DHCP service:auths=solaris.dhcpmgr.*;help=RtDHCPMngmnt.html -File System Management:::Manage, mount, share file systems:auths=solaris.smf.manage.autofs,solaris.smf.manage.shares.*,solaris.smf.value.shares.*;help=RtFileSysMngmnt.html +File System Management:::Manage, mount, share file systems:profiles=SMB Management;auths=solaris.smf.manage.autofs,solaris.smf.manage.shares.*,solaris.smf.value.shares.*;help=RtFileSysMngmnt.html File System Security:::Manage file system security attributes:help=RtFileSysSecurity.html HAL Management:::Manage HAL SMF service:auths=solaris.smf.manage.hal;help=RtHALMngmnt.html Idmap Name Mapping Management:::Manage Name-based Mapping Rules of Identity Mapping Service:auths=solaris.admin.idmap.rules;help=RtIdmapNameRulesMngmnt.html @@ -75,6 +75,7 @@ Crypto Management:::Cryptographic Framework Administration:help=RtCryptoMngmnt.h Kerberos Client Management:::Maintain and Administer Kerberos excluding the servers:help=RtKerberosClntMngmnt.html Kerberos Server Management:::Maintain and Administer Kerberos Servers:profiles=Kerberos Client Management;help=RtKerberosSrvrMngmnt.html DAT Administration:::Manage the DAT configuration:help=RtDatAdmin.html +SMB Management:::Manage the SMB service:auths=solaris.smf.manage.smb,solaris.smf.value.smb,solaris.smf.read.smb;help=RtSMBMngmnt.html ZFS File System Management:::Create and Manage ZFS File Systems:help=RtZFSFileSysMngmnt.html ZFS Storage Management:::Create and Manage ZFS Storage Pools:help=RtZFSStorageMngmnt.html Zone Management:::Zones Virtual Application Environment Administration:help=RtZoneMngmnt.html diff --git a/usr/src/lib/libshare/Makefile b/usr/src/lib/libshare/Makefile index fb19160065..0c333e4459 100644 --- a/usr/src/lib/libshare/Makefile +++ b/usr/src/lib/libshare/Makefile @@ -35,7 +35,7 @@ $(BUILD64)MACHS += $(MACH64) # Add plugin module directories here. They need to build after the libshare # objects are built. -PLUGINS = nfs +PLUGINS = smb nfs $(PLUGINS): $(MACHS) SUBDIRS = $(MACHS) $(PLUGINS) diff --git a/usr/src/lib/libshare/common/libshare.c b/usr/src/lib/libshare/common/libshare.c index 25213e97a4..49104b2abe 100644 --- a/usr/src/lib/libshare/common/libshare.c +++ b/usr/src/lib/libshare/common/libshare.c @@ -58,6 +58,16 @@ #define SA_STRSIZE 256 /* max string size for names */ /* + * internal object type values returned by sa_get_object_type() + */ +#define SA_TYPE_UNKNOWN 0 +#define SA_TYPE_GROUP 1 +#define SA_TYPE_SHARE 2 +#define SA_TYPE_RESOURCE 3 +#define SA_TYPE_OPTIONSET 4 +#define SA_TYPE_ALTSPACE 5 + +/* * internal data structures */ @@ -69,16 +79,20 @@ extern int gettransients(sa_handle_impl_t, xmlNodePtr *); extern int sa_valid_property(void *, char *, sa_property_t); extern char *sa_fstype(char *); extern int sa_is_share(void *); +extern int sa_is_resource(void *); extern ssize_t scf_max_name_len; /* defined in scfutil during initialization */ extern int sa_group_is_zfs(sa_group_t); extern int sa_path_is_zfs(char *); extern int sa_zfs_set_sharenfs(sa_group_t, char *, int); +extern int sa_zfs_set_sharesmb(sa_group_t, char *, int); extern void update_legacy_config(sa_handle_t); extern int issubdir(char *, char *); extern int sa_zfs_init(sa_handle_impl_t); extern void sa_zfs_fini(sa_handle_impl_t); extern void sablocksigs(sigset_t *); extern void saunblocksigs(sigset_t *); +static sa_group_t sa_get_optionset_parent(sa_optionset_t); +static char *get_node_attr(void *, char *); /* * Data structures for finding/managing the document root to access @@ -188,6 +202,21 @@ sa_errorstr(int err) case SA_NOT_SHARED: ret = dgettext(TEXT_DOMAIN, "not shared"); break; + case SA_NO_SUCH_RESOURCE: + ret = dgettext(TEXT_DOMAIN, "no such resource"); + break; + case SA_RESOURCE_REQUIRED: + ret = dgettext(TEXT_DOMAIN, "resource name required"); + break; + case SA_MULTIPLE_ERROR: + ret = dgettext(TEXT_DOMAIN, "errors from multiple protocols"); + break; + case SA_PATH_IS_SUBDIR: + ret = dgettext(TEXT_DOMAIN, "path is a subpath of share"); + break; + case SA_PATH_IS_PARENTDIR: + ret = dgettext(TEXT_DOMAIN, "path is parent of a share"); + break; default: (void) snprintf(errstr, sizeof (errstr), dgettext(TEXT_DOMAIN, "unknown %d"), err); @@ -376,6 +405,36 @@ is_shared(sa_share_t share) } /* + * excluded_protocol(share, proto) + * + * Returns B_TRUE if the specified protocol appears in the "exclude" + * property. This is used to prevent sharing special case shares + * (e.g. subdirs when SMB wants a subdir and NFS doesn't. B_FALSE is + * returned if the protocol isn't in the list. + */ +static boolean_t +excluded_protocol(sa_share_t share, char *proto) +{ + char *protolist; + char *str; + char *token; + + protolist = sa_get_share_attr(share, "exclude"); + if (protolist != NULL) { + str = protolist; + while ((token = strtok(str, ",")) != NULL) { + if (strcmp(token, proto) == 0) { + sa_free_attr_string(protolist); + return (B_TRUE); + } + str = NULL; + } + sa_free_attr_string(protolist); + } + return (B_FALSE); +} + +/* * checksubdirgroup(group, newpath, strictness) * * check all the specified newpath against all the paths in the @@ -392,6 +451,11 @@ checksubdirgroup(sa_group_t group, char *newpath, int strictness) sa_share_t share; char *path; int issub = SA_OK; + int subdir; + int parent; + + if (newpath == NULL) + return (SA_INVALID_PATH); for (share = sa_get_share(group, NULL); share != NULL; share = sa_get_next_share(share)) { @@ -417,13 +481,18 @@ checksubdirgroup(sa_group_t group, char *newpath, int strictness) */ if (path == NULL) continue; - if (newpath != NULL && - (strcmp(path, newpath) == 0 || issubdir(newpath, path) || - issubdir(path, newpath))) { - sa_free_attr_string(path); - path = NULL; + + if (strcmp(path, newpath) == 0) { issub = SA_INVALID_PATH; - break; + } else { + subdir = issubdir(newpath, path); + parent = issubdir(path, newpath); + if (subdir || parent) { + sa_free_attr_string(path); + path = NULL; + return (subdir ? + SA_PATH_IS_SUBDIR : SA_PATH_IS_PARENTDIR); + } } sa_free_attr_string(path); path = NULL; @@ -446,15 +515,16 @@ static int checksubdir(sa_handle_t handle, char *newpath, int strictness) { sa_group_t group; - int issub; + int issub = SA_OK; char *path = NULL; - for (issub = 0, group = sa_get_group(handle, NULL); - group != NULL && !issub; group = sa_get_next_group(group)) { + for (group = sa_get_group(handle, NULL); + group != NULL && issub == SA_OK; + group = sa_get_next_group(group)) { if (sa_group_is_zfs(group)) { sa_group_t subgroup; for (subgroup = sa_get_sub_group(group); - subgroup != NULL && !issub; + subgroup != NULL && issub == SA_OK; subgroup = sa_get_next_group(subgroup)) issub = checksubdirgroup(subgroup, newpath, strictness); @@ -517,14 +587,17 @@ validpath(sa_handle_t handle, char *path, int strictness) /* * check to see if group/share is persistent. + * + * "group" can be either an sa_group_t or an sa_share_t. (void *) + * works since both thse types are also void *. */ -static int -is_persistent(sa_group_t group) +int +sa_is_persistent(void *group) { char *type; int persist = 1; - type = sa_get_group_attr(group, "type"); + type = sa_get_group_attr((sa_group_t)group, "type"); if (type != NULL && strcmp(type, "transient") == 0) persist = 0; if (type != NULL) @@ -590,6 +663,35 @@ is_zfs_group(sa_group_t group) } /* + * sa_get_object_type(object) + * + * This function returns a numeric value representing the object + * type. This allows using simpler checks when doing type specific + * operations. + */ + +static int +sa_get_object_type(void *object) +{ + xmlNodePtr node = (xmlNodePtr)object; + int type; + + if (xmlStrcmp(node->name, (xmlChar *)"group") == 0) + type = SA_TYPE_GROUP; + else if (xmlStrcmp(node->name, (xmlChar *)"share") == 0) + type = SA_TYPE_SHARE; + else if (xmlStrcmp(node->name, (xmlChar *)"resource") == 0) + type = SA_TYPE_RESOURCE; + else if (xmlStrcmp(node->name, (xmlChar *)"optionset") == 0) + type = SA_TYPE_OPTIONSET; + else if (xmlStrcmp(node->name, (xmlChar *)"security") == 0) + type = SA_TYPE_ALTSPACE; + else + assert(0); + return (type); +} + +/* * sa_optionset_name(optionset, oname, len, id) * return the SMF name for the optionset. If id is not NULL, it * will have the GUID value for a share and should be used @@ -605,15 +707,34 @@ static int sa_optionset_name(sa_optionset_t optionset, char *oname, size_t len, char *id) { char *proto; + void *parent; + int ptype; if (id == NULL) id = "optionset"; - proto = sa_get_optionset_attr(optionset, "type"); - len = snprintf(oname, len, "%s_%s", id, proto ? proto : "default"); + parent = sa_get_optionset_parent(optionset); + if (parent != NULL) { + ptype = sa_get_object_type(parent); + proto = sa_get_optionset_attr(optionset, "type"); + if (ptype != SA_TYPE_RESOURCE) { + len = snprintf(oname, len, "%s_%s", id, + proto ? proto : "default"); + } else { + char *index; + index = get_node_attr((void *)parent, "id"); + if (index != NULL) + len = snprintf(oname, len, "%s_%s_%s", id, + proto ? proto : "default", index); + else + len = 0; + } - if (proto != NULL) - sa_free_attr_string(proto); + if (proto != NULL) + sa_free_attr_string(proto); + } else { + len = 0; + } return (len); } @@ -660,6 +781,7 @@ verifydefgroupopts(sa_handle_t handle) { sa_group_t defgrp; sa_optionset_t opt; + defgrp = sa_get_group(handle, "default"); if (defgrp != NULL) { opt = sa_get_optionset(defgrp, NULL); @@ -1187,7 +1309,7 @@ sa_find_share(sa_handle_t handle, char *sharepath) /* * sa_check_path(group, path, strictness) * - * check that path is a valid path relative to the group. Currently, + * Check that path is a valid path relative to the group. Currently, * we are ignoring the group and checking only the NFS rules. Later, * we may want to use the group to then check against the protocols * enabled on the group. The strictness values mean: @@ -1206,16 +1328,84 @@ sa_check_path(sa_group_t group, char *path, int strictness) } /* - * _sa_add_share(group, sharepath, persist, *error) + * mark_excluded_protos(group, share, flags) + * + * Walk through all the protocols enabled for the group and check to + * see if the share has any of them should be in the exclude list + * based on the featureset of the protocol. If there are any, add the + * "exclude" property to the share. + */ +static void +mark_excluded_protos(sa_group_t group, xmlNodePtr share, uint64_t flags) +{ + sa_optionset_t optionset; + char exclude_list[SA_STRSIZE]; + char *sep = ""; + + exclude_list[0] = '\0'; + for (optionset = sa_get_optionset(group, NULL); + optionset != NULL; + optionset = sa_get_next_optionset(optionset)) { + char *value; + uint64_t features; + value = sa_get_optionset_attr(optionset, "type"); + if (value == NULL) + continue; + features = sa_proto_get_featureset(value); + sa_free_attr_string(value); + if (!(features & flags)) { + (void) strlcat(exclude_list, sep, + sizeof (exclude_list)); + (void) strlcat(exclude_list, value, + sizeof (exclude_list)); + sep = ","; + } + } + if (exclude_list[0] != '\0') + xmlSetProp(share, (xmlChar *)"exclude", + (xmlChar *)exclude_list); +} + +/* + * get_all_features(group) * - * common code for all types of add_share. sa_add_share() is the + * Walk through all the protocols on the group and collect all + * possible enabled features. This is the OR of all the featuresets. + */ +static uint64_t +get_all_features(sa_group_t group) +{ + sa_optionset_t optionset; + uint64_t features = 0; + + for (optionset = sa_get_optionset(group, NULL); + optionset != NULL; + optionset = sa_get_next_optionset(optionset)) { + char *value; + value = sa_get_optionset_attr(optionset, "type"); + if (value == NULL) + continue; + features |= sa_proto_get_featureset(value); + sa_free_attr_string(value); + } + return (features); +} + + +/* + * _sa_add_share(group, sharepath, persist, *error, flags) + * + * Common code for all types of add_share. sa_add_share() is the * public API, we also need to be able to do this when parsing legacy * files and construction of the internal configuration while - * extracting config info from SMF. + * extracting config info from SMF. "flags" indicates if some + * protocols need relaxed rules while other don't. These values are + * the featureset values defined in libshare.h. */ sa_share_t -_sa_add_share(sa_group_t group, char *sharepath, int persist, int *error) +_sa_add_share(sa_group_t group, char *sharepath, int persist, int *error, + uint64_t flags) { xmlNodePtr node = NULL; int err; @@ -1223,52 +1413,60 @@ _sa_add_share(sa_group_t group, char *sharepath, int persist, int *error) err = SA_OK; /* assume success */ node = xmlNewChild((xmlNodePtr)group, NULL, (xmlChar *)"share", NULL); - if (node != NULL) { - xmlSetProp(node, (xmlChar *)"path", (xmlChar *)sharepath); - xmlSetProp(node, (xmlChar *)"type", - persist ? (xmlChar *)"persist" : (xmlChar *)"transient"); - if (persist != SA_SHARE_TRANSIENT) { - /* - * persistent shares come in two flavors: SMF and - * ZFS. Sort this one out based on target group and - * path type. Currently, only NFS is supported in the - * ZFS group and it is always on. - */ - if (sa_group_is_zfs(group) && - sa_path_is_zfs(sharepath)) { + if (node == NULL) { + if (error != NULL) + *error = SA_NO_MEMORY; + return (node); + } + + xmlSetProp(node, (xmlChar *)"path", (xmlChar *)sharepath); + xmlSetProp(node, (xmlChar *)"type", + persist ? (xmlChar *)"persist" : (xmlChar *)"transient"); + if (flags != 0) + mark_excluded_protos(group, node, flags); + if (persist != SA_SHARE_TRANSIENT) { + /* + * persistent shares come in two flavors: SMF and + * ZFS. Sort this one out based on target group and + * path type. Both NFS and SMB are supported. First, + * check to see if the protocol is enabled on the + * subgroup and then setup the share appropriately. + */ + if (sa_group_is_zfs(group) && + sa_path_is_zfs(sharepath)) { + if (sa_get_optionset(group, "nfs") != NULL) err = sa_zfs_set_sharenfs(group, sharepath, 1); + else if (sa_get_optionset(group, "smb") != NULL) + err = sa_zfs_set_sharesmb(group, sharepath, 1); + } else { + sa_handle_impl_t impl_handle; + impl_handle = + (sa_handle_impl_t)sa_find_group_handle(group); + if (impl_handle != NULL) { + err = sa_commit_share(impl_handle->scfhandle, + group, (sa_share_t)node); } else { - sa_handle_impl_t impl_handle; - impl_handle = - (sa_handle_impl_t)sa_find_group_handle( - group); - if (impl_handle != NULL) { - err = sa_commit_share( - impl_handle->scfhandle, group, - (sa_share_t)node); - } else { - err = SA_SYSTEM_ERR; - } + err = SA_SYSTEM_ERR; } } - if (err == SA_NO_PERMISSION && persist & SA_SHARE_PARSER) { - /* called by the dfstab parser so could be a show */ - err = SA_OK; - } - if (err != SA_OK) { - /* - * we couldn't commit to the repository so undo - * our internal state to reflect reality. - */ - xmlUnlinkNode(node); - xmlFreeNode(node); - node = NULL; - } - } else { - err = SA_NO_MEMORY; } + if (err == SA_NO_PERMISSION && persist & SA_SHARE_PARSER) + /* called by the dfstab parser so could be a show */ + err = SA_OK; + + if (err != SA_OK) { + /* + * we couldn't commit to the repository so undo + * our internal state to reflect reality. + */ + xmlUnlinkNode(node); + xmlFreeNode(node); + node = NULL; + } + if (error != NULL) *error = err; + return (node); } @@ -1285,9 +1483,10 @@ sa_share_t sa_add_share(sa_group_t group, char *sharepath, int persist, int *error) { xmlNodePtr node = NULL; - sa_share_t dup; int strictness = SA_CHECK_NORMAL; sa_handle_t handle; + uint64_t special = 0; + uint64_t features; /* * If the share is to be permanent, use strict checking so a @@ -1304,12 +1503,33 @@ sa_add_share(sa_group_t group, char *sharepath, int persist, int *error) handle = sa_find_group_handle(group); - if ((dup = sa_find_share(handle, sharepath)) == NULL && - (*error = sa_check_path(group, sharepath, strictness)) == SA_OK) { - node = _sa_add_share(group, sharepath, persist, error); - } - if (dup != NULL) + /* + * need to determine if the share is valid. The rules are: + * - The path must not already exist + * - The path must not be a subdir or parent dir of an + * existing path unless at least one protocol allows it. + * The sub/parent check is done in sa_check_path(). + */ + + if (sa_find_share(handle, sharepath) == NULL) { + *error = sa_check_path(group, sharepath, strictness); + features = get_all_features(group); + switch (*error) { + case SA_PATH_IS_SUBDIR: + if (features & SA_FEATURE_ALLOWSUBDIRS) + special |= SA_FEATURE_ALLOWSUBDIRS; + break; + case SA_PATH_IS_PARENTDIR: + if (features & SA_FEATURE_ALLOWPARDIRS) + special |= SA_FEATURE_ALLOWPARDIRS; + break; + } + if (*error == SA_OK || special != SA_FEATURE_NONE) + node = _sa_add_share(group, sharepath, persist, + error, special); + } else { *error = SA_DUPLICATE_NAME; + } return ((sa_share_t)node); } @@ -1324,28 +1544,52 @@ sa_enable_share(sa_share_t share, char *protocol) { char *sharepath; struct stat st; - int err = 0; + int err = SA_OK; + int ret; sharepath = sa_get_share_attr(share, "path"); + if (sharepath == NULL) + return (SA_NO_MEMORY); if (stat(sharepath, &st) < 0) { err = SA_NO_SUCH_PATH; } else { /* tell the server about the share */ if (protocol != NULL) { + if (excluded_protocol(share, protocol)) + goto done; + /* lookup protocol specific handler */ err = sa_proto_share(protocol, share); if (err == SA_OK) - (void) sa_set_share_attr(share, "shared", - "true"); + (void) sa_set_share_attr(share, + "shared", "true"); } else { - /* - * Tell all protocols. Only NFS for now but - * SMB is coming. - */ - err = sa_proto_share("nfs", share); + /* Tell all protocols about the share */ + sa_group_t group; + sa_optionset_t optionset; + + group = sa_get_parent_group(share); + + for (optionset = sa_get_optionset(group, NULL); + optionset != NULL; + optionset = sa_get_next_optionset(optionset)) { + char *proto; + proto = sa_get_optionset_attr(optionset, + "type"); + if (proto != NULL) { + if (!excluded_protocol(share, proto)) { + ret = sa_proto_share(proto, + share); + if (ret != SA_OK) + err = ret; + } + sa_free_attr_string(proto); + } + } (void) sa_set_share_attr(share, "shared", "true"); } } +done: if (sharepath != NULL) sa_free_attr_string(sharepath); return (err); @@ -1353,31 +1597,47 @@ sa_enable_share(sa_share_t share, char *protocol) /* * sa_disable_share(share, protocol) - * Disable the specified share to the specified protocol. - * If protocol is NULL, then all protocols. + * Disable the specified share to the specified protocol. If + * protocol is NULL, then all protocols that are enabled for the + * share should be disabled. */ int sa_disable_share(sa_share_t share, char *protocol) { char *path; - char *shared; + int err = SA_OK; int ret = SA_OK; path = sa_get_share_attr(share, "path"); - shared = sa_get_share_attr(share, "shared"); if (protocol != NULL) { ret = sa_proto_unshare(share, protocol, path); } else { /* need to do all protocols */ - ret = sa_proto_unshare(share, "nfs", path); + sa_group_t group; + sa_optionset_t optionset; + + group = sa_get_parent_group(share); + + /* Tell all protocols about the share */ + for (optionset = sa_get_optionset(group, NULL); + optionset != NULL; + optionset = sa_get_next_optionset(optionset)) { + char *proto; + + proto = sa_get_optionset_attr(optionset, "type"); + if (proto != NULL) { + err = sa_proto_unshare(share, proto, path); + if (err != SA_OK) + ret = err; + sa_free_attr_string(proto); + } + } } if (ret == SA_OK) (void) sa_set_share_attr(share, "shared", NULL); if (path != NULL) sa_free_attr_string(path); - if (shared != NULL) - sa_free_attr_string(shared); return (ret); } @@ -1415,7 +1675,7 @@ sa_remove_share(sa_share_t share) /* only do SMF action if permanent */ if (!transient || zfs != NULL) { /* remove from legacy dfstab as well as possible SMF */ - ret = sa_delete_legacy(share); + ret = sa_delete_legacy(share, NULL); if (ret == SA_OK) { if (!sa_group_is_zfs(group)) { sa_handle_impl_t impl_handle; @@ -1497,7 +1757,7 @@ sa_move_share(sa_group_t group, sa_share_t share) /* * sa_get_parent_group(share) * - * Return the containg group for the share. If a group was actually + * Return the containing group for the share. If a group was actually * passed in, we don't want a parent so return NULL. */ @@ -1728,7 +1988,7 @@ sa_update_config(sa_handle_t handle) /* * get_node_attr(node, tag) * - * Get the speficied tag(attribute) if it exists on the node. This is + * Get the specified tag(attribute) if it exists on the node. This is * used internally by a number of attribute oriented functions. */ @@ -1746,7 +2006,7 @@ get_node_attr(void *nodehdl, char *tag) /* * get_node_attr(node, tag) * - * Set the speficied tag(attribute) to the specified value This is + * Set the specified tag(attribute) to the specified value This is * used internally by a number of attribute oriented functions. It * doesn't update the repository, only the internal document state. */ @@ -1792,6 +2052,14 @@ sa_set_group_attr(sa_group_t group, char *tag, char *value) char *groupname; sa_handle_impl_t impl_handle; + /* + * ZFS group/subgroup doesn't need the handle so shortcut. + */ + if (sa_group_is_zfs(group)) { + set_node_attr((void *)group, tag, value); + return (SA_OK); + } + impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); if (impl_handle != NULL) { groupname = sa_get_group_attr(group, "name"); @@ -1833,47 +2101,15 @@ sa_get_share_attr(sa_share_t share, char *tag) } /* - * sa_get_resource(group, resource) - * - * Search all the shares in the speified group for a share with a - * resource name matching the one specified. - * - * In the future, it may be advantageous to allow group to be NULL and - * search all groups but that isn't needed at present. - */ - -sa_share_t -sa_get_resource(sa_group_t group, char *resource) -{ - sa_share_t share = NULL; - char *name = NULL; - - if (resource != NULL) { - for (share = sa_get_share(group, NULL); share != NULL; - share = sa_get_next_share(share)) { - name = sa_get_share_attr(share, "resource"); - if (name != NULL) { - if (strcmp(name, resource) == 0) - break; - sa_free_attr_string(name); - name = NULL; - } - } - if (name != NULL) - sa_free_attr_string(name); - } - return ((sa_share_t)share); -} - -/* * _sa_set_share_description(share, description) * - * Add a description tag with text contents to the specified share. - * A separate XML tag is used rather than a property. + * Add a description tag with text contents to the specified share. A + * separate XML tag is used rather than a property. This can also be + * used with resources. */ xmlNodePtr -_sa_set_share_description(sa_share_t share, char *content) +_sa_set_share_description(void *share, char *content) { xmlNodePtr node; node = xmlNewChild((xmlNodePtr)share, NULL, (xmlChar *)"description", @@ -2205,7 +2441,6 @@ sa_set_share_description(sa_share_t share, char *content) break; } } - group = sa_get_parent_group(share); /* no existing description but want to add */ if (node == NULL && content != NULL) { /* add a description */ @@ -2218,7 +2453,8 @@ sa_set_share_description(sa_share_t share, char *content) xmlUnlinkNode(node); xmlFreeNode(node); } - if (group != NULL && is_persistent((sa_group_t)share)) { + group = sa_get_parent_group(share); + if (group != NULL && sa_is_persistent(share)) { sa_handle_impl_t impl_handle; impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); if (impl_handle != NULL) { @@ -2269,7 +2505,7 @@ sa_get_share_description(sa_share_t share) } } if (node != NULL) { - description = xmlNodeGetContent((xmlNodePtr)share); + description = xmlNodeGetContent(node); fixproblemchars((char *)description); } return ((char *)description); @@ -2299,12 +2535,44 @@ sa_create_optionset(sa_group_t group, char *proto) { sa_optionset_t optionset; sa_group_t parent = group; + sa_share_t share = NULL; + int err = SA_OK; + char *id = NULL; optionset = sa_get_optionset(group, proto); if (optionset != NULL) { /* can't have a duplicate protocol */ optionset = NULL; } else { + /* + * Account for resource names being slightly + * different. + */ + if (sa_is_share(group)) { + /* + * Transient shares do not have an "id" so not an + * error to not find one. + */ + id = sa_get_share_attr((sa_share_t)group, "id"); + } else if (sa_is_resource(group)) { + share = sa_get_resource_parent( + (sa_resource_t)group); + id = sa_get_resource_attr(share, "id"); + + /* id can be NULL if the group is transient (ZFS) */ + if (id == NULL && sa_is_persistent(group)) + err = SA_NO_MEMORY; + } + if (err == SA_NO_MEMORY) { + /* + * Couldn't get the id for the share or + * resource. While this could be a + * configuration issue, it is most likely an + * out of memory. In any case, fail the create. + */ + return (NULL); + } + optionset = (sa_optionset_t)xmlNewChild((xmlNodePtr)group, NULL, (xmlChar *)"optionset", NULL); /* @@ -2314,38 +2582,44 @@ sa_create_optionset(sa_group_t group, char *proto) if (optionset != NULL) { char oname[SA_STRSIZE]; char *groupname; - char *id = NULL; - if (sa_is_share(group)) + /* + * Need to get parent group in all cases, but also get + * the share if this is a resource. + */ + if (sa_is_share(group)) { parent = sa_get_parent_group((sa_share_t)group); + } else if (sa_is_resource(group)) { + share = sa_get_resource_parent( + (sa_resource_t)group); + parent = sa_get_parent_group(share); + } sa_set_optionset_attr(optionset, "type", proto); - if (sa_is_share(group)) { - id = sa_get_share_attr((sa_share_t)group, "id"); - } (void) sa_optionset_name(optionset, oname, sizeof (oname), id); groupname = sa_get_group_attr(parent, "name"); - if (groupname != NULL && is_persistent(group)) { + if (groupname != NULL && sa_is_persistent(group)) { sa_handle_impl_t impl_handle; - impl_handle = (sa_handle_impl_t) - sa_find_group_handle(group); + impl_handle = + (sa_handle_impl_t)sa_find_group_handle( + group); assert(impl_handle != NULL); if (impl_handle != NULL) { (void) sa_get_instance( - impl_handle->scfhandle, - groupname); + impl_handle->scfhandle, groupname); (void) sa_create_pgroup( impl_handle->scfhandle, oname); } } if (groupname != NULL) sa_free_attr_string(groupname); - if (id != NULL) - sa_free_attr_string(id); } } + + if (id != NULL) + sa_free_attr_string(id); return (optionset); } @@ -2388,7 +2662,7 @@ sa_get_optionset_parent(sa_optionset_t optionset) * * In order to avoid making multiple updates to a ZFS share when * setting properties, the share attribute "changed" will be set to - * true when a property is added or modifed. When done adding + * true when a property is added or modified. When done adding * properties, we can then detect that an update is needed. We then * clear the state here to detect additional changes. */ @@ -2470,7 +2744,7 @@ sa_commit_properties(sa_optionset_t optionset, int clear) /* * sa_destroy_optionset(optionset) * - * Remove the optionset from its group. Update the repostory to + * Remove the optionset from its group. Update the repository to * reflect this change. */ @@ -2486,9 +2760,16 @@ sa_destroy_optionset(sa_optionset_t optionset) /* now delete the prop group */ group = sa_get_optionset_parent(optionset); - if (group != NULL && sa_is_share(group)) { - ispersist = is_persistent(group); - id = sa_get_share_attr((sa_share_t)group, "id"); + if (group != NULL) { + if (sa_is_resource(group)) { + sa_resource_t resource = group; + sa_share_t share = sa_get_resource_parent(resource); + group = sa_get_parent_group(share); + id = sa_get_share_attr(share, "id"); + } else if (sa_is_share(group)) { + id = sa_get_share_attr((sa_share_t)group, "id"); + } + ispersist = sa_is_persistent(group); } if (ispersist) { sa_handle_impl_t impl_handle; @@ -2559,7 +2840,7 @@ sa_create_security(sa_group_t group, char *sectype, char *proto) sa_set_security_attr(security, "sectype", sectype); (void) sa_security_name(security, oname, sizeof (oname), id); - if (groupname != NULL && is_persistent(group)) { + if (groupname != NULL && sa_is_persistent(group)) { sa_handle_impl_t impl_handle; impl_handle = (sa_handle_impl_t)sa_find_group_handle( @@ -2603,7 +2884,7 @@ sa_destroy_security(sa_security_t security) if (group != NULL && !iszfs) { if (sa_is_share(group)) - ispersist = is_persistent(group); + ispersist = sa_is_persistent(group); id = sa_get_share_attr((sa_share_t)group, "id"); } if (ispersist) { @@ -2666,7 +2947,6 @@ is_nodetype(void *node, char *type) return (strcmp((char *)((xmlNodePtr)node)->name, type) == 0); } - /* * add_or_update() * @@ -2721,12 +3001,12 @@ sa_set_prop_by_prop(sa_optionset_t optionset, sa_group_t group, int opttype; /* 1 == optionset, 0 == security */ char *id = NULL; int iszfs = 0; - int isshare = 0; sa_group_t parent = NULL; + sa_share_t share = NULL; sa_handle_impl_t impl_handle; scfutilhandle_t *scf_handle; - if (!is_persistent(group)) { + if (!sa_is_persistent(group)) { /* * if the group/share is not persistent we don't need * to do anything here @@ -2742,12 +3022,20 @@ sa_set_prop_by_prop(sa_optionset_t optionset, sa_group_t group, entry = scf_entry_create(scf_handle->handle); opttype = is_nodetype((void *)optionset, "optionset"); + /* + * Check for share vs. resource since they need slightly + * different treatment given the hierarchy. + */ if (valstr != NULL && entry != NULL) { if (sa_is_share(group)) { - isshare = 1; parent = sa_get_parent_group(group); + share = (sa_share_t)group; if (parent != NULL) iszfs = is_zfs_group(parent); + } else if (sa_is_resource(group)) { + share = sa_get_parent_group(group); + if (share != NULL) + parent = sa_get_parent_group(share); } else { iszfs = is_zfs_group(group); } @@ -2755,15 +3043,13 @@ sa_set_prop_by_prop(sa_optionset_t optionset, sa_group_t group, if (scf_handle->trans == NULL) { char oname[SA_STRSIZE]; char *groupname = NULL; - if (isshare) { - if (parent != NULL) { + if (share != NULL) { + if (parent != NULL) groupname = sa_get_group_attr(parent, "name"); - } - id = - sa_get_share_attr((sa_share_t)group, - "id"); + id = sa_get_share_attr( + (sa_share_t)share, "id"); } else { groupname = sa_get_group_attr(group, "name"); @@ -2870,14 +3156,22 @@ sa_add_property(void *object, sa_property_t property) sa_free_attr_string(proto); parent = sa_get_parent_group(object); - if (!is_persistent(parent)) { + if (!sa_is_persistent(parent)) return (ret); - } - if (sa_is_share(parent)) + if (sa_is_resource(parent)) { + /* + * Resources are children of share. Need to go up two + * levels to find the group but the parent needs to be + * the share at this point in order to get the "id". + */ + parent = sa_get_parent_group(parent); group = sa_get_parent_group(parent); - else + } else if (sa_is_share(parent)) { + group = sa_get_parent_group(parent); + } else { group = parent; + } if (property == NULL) { ret = SA_NO_MEMORY; @@ -3110,7 +3404,7 @@ sa_set_protocol_property(sa_property_t prop, char *value) /* * sa_add_protocol_property(propset, prop) * - * Add a new property to the protocol sepcific property set. + * Add a new property to the protocol specific property set. */ int @@ -3128,7 +3422,7 @@ sa_add_protocol_property(sa_protocol_properties_t propset, sa_property_t prop) /* * sa_create_protocol_properties(proto) * - * Create a protocol specifity property set. + * Create a protocol specific property set. */ sa_protocol_properties_t @@ -3141,3 +3435,583 @@ sa_create_protocol_properties(char *proto) xmlSetProp(node, (xmlChar *)"type", (xmlChar *)proto); return (node); } + +/* + * sa_get_share_resource(share, resource) + * + * Get the named resource from the share, if it exists. If resource is + * NULL, get the first resource. + */ + +sa_resource_t +sa_get_share_resource(sa_share_t share, char *resource) +{ + xmlNodePtr node = NULL; + xmlChar *name; + + if (share != NULL) { + for (node = ((xmlNodePtr)share)->children; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"resource") == 0) { + if (resource == NULL) { + /* + * We are looking for the first + * resource node and not a names + * resource. + */ + break; + } else { + /* is it the correct share? */ + name = xmlGetProp(node, + (xmlChar *)"name"); + if (name != NULL && + xmlStrcasecmp(name, + (xmlChar *)resource) == 0) { + xmlFree(name); + break; + } + xmlFree(name); + } + } + } + } + return ((sa_resource_t)node); +} + +/* + * sa_get_next_resource(resource) + * Return the next share following the specified share + * from the internal list of shares. Returns NULL if there + * are no more shares. The list is relative to the same + * group. + */ +sa_share_t +sa_get_next_resource(sa_resource_t resource) +{ + xmlNodePtr node = NULL; + + if (resource != NULL) { + for (node = ((xmlNodePtr)resource)->next; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"resource") == 0) + break; + } + } + return ((sa_share_t)node); +} + +/* + * _sa_get_next_resource_index(share) + * + * get the next resource index number (one greater then current largest) + */ + +static int +_sa_get_next_resource_index(sa_share_t share) +{ + sa_resource_t resource; + int index = 0; + char *id; + + for (resource = sa_get_share_resource(share, NULL); + resource != NULL; + resource = sa_get_next_resource(resource)) { + id = get_node_attr((void *)resource, "id"); + if (id != NULL) { + int val; + val = atoi(id); + if (val > index) + index = val; + sa_free_attr_string(id); + } + } + return (index + 1); +} + + +/* + * sa_add_resource(share, resource, persist, &err) + * + * Adds a new resource name associated with share. The resource name + * must be unique in the system and will be case insensitive (eventually). + */ + +sa_resource_t +sa_add_resource(sa_share_t share, char *resource, int persist, int *error) +{ + xmlNodePtr node; + int err = SA_OK; + sa_resource_t res; + sa_group_t group; + sa_handle_t handle; + char istring[8]; /* just big enough for an integer value */ + int index; + + group = sa_get_parent_group(share); + handle = sa_find_group_handle(group); + res = sa_find_resource(handle, resource); + if (res != NULL) { + err = SA_DUPLICATE_NAME; + res = NULL; + } else { + node = xmlNewChild((xmlNodePtr)share, NULL, + (xmlChar *)"resource", NULL); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"name", + (xmlChar *)resource); + xmlSetProp(node, (xmlChar *)"type", persist ? + (xmlChar *)"persist" : (xmlChar *)"transient"); + if (persist != SA_SHARE_TRANSIENT) { + index = _sa_get_next_resource_index(share); + (void) snprintf(istring, sizeof (istring), "%d", + index); + xmlSetProp(node, (xmlChar *)"id", + (xmlChar *)istring); + if (!sa_group_is_zfs(group) && + sa_is_persistent((sa_group_t)share)) { + /* ZFS doesn't use resource names */ + sa_handle_impl_t ihandle; + ihandle = (sa_handle_impl_t) + sa_find_group_handle( + group); + if (ihandle != NULL) + err = sa_commit_share( + ihandle->scfhandle, group, + share); + else + err = SA_SYSTEM_ERR; + } + } + } + } + if (error != NULL) + *error = err; + return ((sa_resource_t)node); +} + +/* + * sa_remove_resource(resource) + * + * Remove the resource name from the share (and the system) + */ + +int +sa_remove_resource(sa_resource_t resource) +{ + sa_share_t share; + sa_group_t group; + char *type; + int ret = SA_OK; + int transient = 0; + + share = sa_get_resource_parent(resource); + type = sa_get_share_attr(share, "type"); + group = sa_get_parent_group(share); + + + if (type != NULL) { + if (strcmp(type, "persist") != 0) + transient = 1; + sa_free_attr_string(type); + } + + /* Remove from the share */ + xmlUnlinkNode((xmlNode *)resource); + xmlFreeNode((xmlNode *)resource); + + /* only do SMF action if permanent and not ZFS */ + if (!transient && !sa_group_is_zfs(group)) { + sa_handle_impl_t ihandle; + ihandle = (sa_handle_impl_t)sa_find_group_handle(group); + if (ihandle != NULL) + ret = sa_commit_share(ihandle->scfhandle, group, share); + else + ret = SA_SYSTEM_ERR; + } + return (ret); +} + +/* + * proto_resource_rename(handle, group, resource, newname) + * + * Helper function for sa_rename_resource that notifies the protocol + * of a resource name change prior to a config repository update. + */ +static int +proto_rename_resource(sa_handle_t handle, sa_group_t group, + sa_resource_t resource, char *newname) +{ + sa_optionset_t optionset; + int ret = SA_OK; + int err; + + for (optionset = sa_get_optionset(group, NULL); + optionset != NULL; + optionset = sa_get_next_optionset(optionset)) { + char *type; + type = sa_get_optionset_attr(optionset, "type"); + if (type != NULL) { + err = sa_proto_rename_resource(handle, type, resource, + newname); + if (err != SA_OK) + ret = err; + sa_free_attr_string(type); + } + } + return (ret); +} + +/* + * sa_rename_resource(resource, newname) + * + * Rename the resource to the new name, if it is unique. + */ + +int +sa_rename_resource(sa_resource_t resource, char *newname) +{ + sa_share_t share; + sa_group_t group = NULL; + sa_resource_t target; + int ret = SA_CONFIG_ERR; + sa_handle_t handle = NULL; + + share = sa_get_resource_parent(resource); + if (share == NULL) + return (ret); + + group = sa_get_parent_group(share); + if (group == NULL) + return (ret); + + handle = (sa_handle_impl_t)sa_find_group_handle(group); + if (handle == NULL) + return (ret); + + target = sa_find_resource(handle, newname); + if (target != NULL) { + ret = SA_DUPLICATE_NAME; + } else { + /* + * Everything appears to be valid at this + * point. Change the name of the active share and then + * update the share in the appropriate repository. + */ + ret = proto_rename_resource(handle, group, resource, newname); + set_node_attr(resource, "name", newname); + if (!sa_group_is_zfs(group) && + sa_is_persistent((sa_group_t)share)) { + sa_handle_impl_t ihandle = (sa_handle_impl_t)handle; + ret = sa_commit_share(ihandle->scfhandle, group, + share); + } + } + return (ret); +} + +/* + * sa_get_resource_attr(resource, tag) + * + * Get the named attribute of the resource. "name" and "id" are + * currently defined. NULL if tag not defined. + */ + +char * +sa_get_resource_attr(sa_resource_t resource, char *tag) +{ + return (get_node_attr((void *)resource, tag)); +} + +/* + * sa_set_resource_attr(resource, tag, value) + * + * Get the named attribute of the resource. "name" and "id" are + * currently defined. NULL if tag not defined. Currently we don't do + * much, but additional checking may be needed in the future. + */ + +int +sa_set_resource_attr(sa_resource_t resource, char *tag, char *value) +{ + set_node_attr((void *)resource, tag, value); + return (SA_OK); +} + +/* + * sa_get_resource_parent(resource_t) + * + * Returns the share associated with the resource. + */ + +sa_share_t +sa_get_resource_parent(sa_resource_t resource) +{ + sa_share_t share = NULL; + + if (resource != NULL) + share = (sa_share_t)((xmlNodePtr)resource)->parent; + return (share); +} + +/* + * find_resource(group, name) + * + * Find the resource within the group. + */ + +static sa_resource_t +find_resource(sa_group_t group, char *resname) +{ + sa_share_t share; + sa_resource_t resource = NULL; + char *name; + + /* Iterate over all the shares and resources in the group. */ + for (share = sa_get_share(group, NULL); + share != NULL && resource == NULL; + share = sa_get_next_share(share)) { + for (resource = sa_get_share_resource(share, NULL); + resource != NULL; + resource = sa_get_next_resource(resource)) { + name = sa_get_resource_attr(resource, "name"); + if (name != NULL && xmlStrcasecmp((xmlChar*)name, + (xmlChar*)resname) == 0) { + sa_free_attr_string(name); + break; + } + if (name != NULL) { + sa_free_attr_string(name); + } + } + } + return (resource); +} + +/* + * sa_find_resource(name) + * + * Find the named resource in the system. + */ + +sa_resource_t +sa_find_resource(sa_handle_t handle, char *name) +{ + sa_group_t group; + sa_group_t zgroup; + sa_resource_t resource = NULL; + + /* + * Iterate over all groups and zfs subgroups and check for + * resource name in them. + */ + for (group = sa_get_group(handle, NULL); group != NULL; + group = sa_get_next_group(group)) { + + if (is_zfs_group(group)) { + for (zgroup = + (sa_group_t)_sa_get_child_node((xmlNodePtr)group, + (xmlChar *)"group"); + zgroup != NULL && resource == NULL; + zgroup = sa_get_next_group(zgroup)) { + resource = find_resource(zgroup, name); + } + } else { + resource = find_resource(group, name); + } + if (resource != NULL) + break; + } + return (resource); +} + +/* + * sa_get_resource(group, resource) + * + * Search all the shares in the specified group for a share with a + * resource name matching the one specified. + * + * In the future, it may be advantageous to allow group to be NULL and + * search all groups but that isn't needed at present. + */ + +sa_resource_t +sa_get_resource(sa_group_t group, char *resource) +{ + sa_share_t share = NULL; + sa_resource_t res = NULL; + + if (resource != NULL) { + for (share = sa_get_share(group, NULL); + share != NULL && res == NULL; + share = sa_get_next_share(share)) { + res = sa_get_share_resource(share, resource); + } + } + return (res); +} + +/* + * sa_enable_resource, protocol) + * Disable the specified share to the specified protocol. + * If protocol is NULL, then all protocols. + */ +int +sa_enable_resource(sa_resource_t resource, char *protocol) +{ + int ret = SA_OK; + char **protocols; + int numproto; + + if (protocol != NULL) { + ret = sa_proto_share_resource(protocol, resource); + } else { + /* need to do all protocols */ + if ((numproto = sa_get_protocols(&protocols)) >= 0) { + int i, err; + for (i = 0; i < numproto; i++) { + err = sa_proto_share_resource( + protocols[i], resource); + if (err != SA_OK) + ret = err; + } + free(protocols); + } + } + if (ret == SA_OK) + (void) sa_set_resource_attr(resource, "shared", NULL); + + return (ret); +} + +/* + * sa_disable_resource(resource, protocol) + * + * Disable the specified share for the specified protocol. If + * protocol is NULL, then all protocols. If the underlying + * protocol doesn't implement disable at the resource level, we + * disable at the share level. + */ +int +sa_disable_resource(sa_resource_t resource, char *protocol) +{ + int ret = SA_OK; + char **protocols; + int numproto; + + if (protocol != NULL) { + ret = sa_proto_unshare_resource(protocol, resource); + if (ret == SA_NOT_IMPLEMENTED) { + sa_share_t parent; + /* + * The protocol doesn't implement unshare + * resource. That implies that resource names are + * simple aliases for this protocol so we need to + * unshare the share. + */ + parent = sa_get_resource_parent(resource); + if (parent != NULL) + ret = sa_disable_share(parent, protocol); + else + ret = SA_CONFIG_ERR; + } + } else { + /* need to do all protocols */ + if ((numproto = sa_get_protocols(&protocols)) >= 0) { + int i, err; + for (i = 0; i < numproto; i++) { + err = sa_proto_unshare_resource(protocols[i], + resource); + if (err == SA_NOT_SUPPORTED) { + sa_share_t parent; + parent = sa_get_resource_parent( + resource); + if (parent != NULL) + err = sa_disable_share(parent, + protocols[i]); + else + err = SA_CONFIG_ERR; + } + if (err != SA_OK) + ret = err; + } + free(protocols); + } + } + if (ret == SA_OK) + (void) sa_set_resource_attr(resource, "shared", NULL); + + return (ret); +} + +/* + * sa_set_resource_description(resource, content) + * + * Set the description of share to content. + */ + +int +sa_set_resource_description(sa_resource_t resource, char *content) +{ + xmlNodePtr node; + sa_group_t group; + sa_share_t share; + int ret = SA_OK; + + for (node = ((xmlNodePtr)resource)->children; + node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"description") == 0) { + break; + } + } + + /* no existing description but want to add */ + if (node == NULL && content != NULL) { + /* add a description */ + node = _sa_set_share_description(resource, content); + } else if (node != NULL && content != NULL) { + /* update a description */ + xmlNodeSetContent(node, (xmlChar *)content); + } else if (node != NULL && content == NULL) { + /* remove an existing description */ + xmlUnlinkNode(node); + xmlFreeNode(node); + } + share = sa_get_resource_parent(resource); + group = sa_get_parent_group(share); + if (group != NULL && sa_is_persistent(share)) { + sa_handle_impl_t impl_handle; + impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); + if (impl_handle != NULL) + ret = sa_commit_share(impl_handle->scfhandle, + group, share); + else + ret = SA_SYSTEM_ERR; + } + return (ret); +} + +/* + * sa_get_resource_description(share) + * + * Return the description text for the specified share if it + * exists. NULL if no description exists. + */ + +char * +sa_get_resource_description(sa_resource_t resource) +{ + xmlChar *description = NULL; + xmlNodePtr node; + + for (node = ((xmlNodePtr)resource)->children; node != NULL; + node = node->next) { + if (xmlStrcmp(node->name, (xmlChar *)"description") == 0) + break; + } + if (node != NULL) { + description = xmlNodeGetContent(node); + fixproblemchars((char *)description); + } + return ((char *)description); +} diff --git a/usr/src/lib/libshare/common/libshare.h b/usr/src/lib/libshare/common/libshare.h index c78b0822c6..bdac6a6d64 100644 --- a/usr/src/lib/libshare/common/libshare.h +++ b/usr/src/lib/libshare/common/libshare.h @@ -37,6 +37,8 @@ extern "C" { #endif +#include <sys/types.h> + /* * Basic datatypes for most functions */ @@ -46,6 +48,7 @@ typedef void *sa_property_t; typedef void *sa_optionset_t; typedef void *sa_security_t; typedef void *sa_protocol_properties_t; +typedef void *sa_resource_t; typedef void *sa_handle_t; /* opaque handle to access core functions */ @@ -77,6 +80,11 @@ typedef void *sa_handle_t; /* opaque handle to access core functions */ #define SA_NOT_SUPPORTED 21 /* operation not supported for proto */ #define SA_PROP_SHARE_ONLY 22 /* property valid on share only */ #define SA_NOT_SHARED 23 /* path is not shared */ +#define SA_NO_SUCH_RESOURCE 24 /* resource not found */ +#define SA_RESOURCE_REQUIRED 25 /* resource name is required */ +#define SA_MULTIPLE_ERROR 26 /* multiple protocols reported error */ +#define SA_PATH_IS_SUBDIR 27 /* check_path found path is subdir */ +#define SA_PATH_IS_PARENTDIR 28 /* check_path found path is parent */ /* API Initialization */ #define SA_INIT_SHARE_API 0x0001 /* init share specific interface */ @@ -90,8 +98,9 @@ typedef void *sa_handle_t; /* opaque handle to access core functions */ */ #define SA_MAX_NAME_LEN 100 /* must fit service instance name */ +#define SA_MAX_RESOURCE_NAME 255 /* Maximum length of resource name */ -/* Used in calls to sa_add_share() */ +/* Used in calls to sa_add_share() and sa_add_resource() */ #define SA_SHARE_TRANSIENT 0 /* shared but not across reboot */ #define SA_SHARE_LEGACY 1 /* share is in dfstab only */ #define SA_SHARE_PERMANENT 2 /* share goes to repository */ @@ -105,6 +114,16 @@ typedef void *sa_handle_t; /* opaque handle to access core functions */ #define SA_RBAC_VALUE "solaris.smf.value.shares" /* + * Feature set bit definitions + */ + +#define SA_FEATURE_NONE 0x0000 /* no feature flags set */ +#define SA_FEATURE_RESOURCE 0x0001 /* resource names are required */ +#define SA_FEATURE_DFSTAB 0x0002 /* need to manage in dfstab */ +#define SA_FEATURE_ALLOWSUBDIRS 0x0004 /* allow subdirs to be shared */ +#define SA_FEATURE_ALLOWPARDIRS 0x0008 /* allow parent dirs to be shared */ + +/* * legacy files */ @@ -143,7 +162,6 @@ extern int sa_check_path(sa_group_t, char *, int); extern int sa_move_share(sa_group_t, sa_share_t); extern int sa_remove_share(sa_share_t); extern sa_share_t sa_get_share(sa_group_t, char *); -extern sa_share_t sa_get_resource(sa_group_t, char *); extern sa_share_t sa_find_share(sa_handle_t, char *); extern sa_share_t sa_get_next_share(sa_share_t); extern char *sa_get_share_attr(sa_share_t, char *); @@ -152,9 +170,26 @@ extern sa_group_t sa_get_parent_group(sa_share_t); extern int sa_set_share_attr(sa_share_t, char *, char *); extern int sa_set_share_description(sa_share_t, char *); extern int sa_enable_share(sa_group_t, char *); -extern int sa_disable_share(sa_group_t, char *); +extern int sa_disable_share(sa_share_t, char *); extern int sa_is_share(void *); +/* resource name related */ +extern sa_resource_t sa_find_resource(sa_handle_t, char *); +extern sa_resource_t sa_get_resource(sa_group_t, char *); +extern sa_resource_t sa_get_next_resource(sa_resource_t); +extern sa_share_t sa_get_resource_parent(sa_resource_t); +extern sa_resource_t sa_get_share_resource(sa_share_t, char *); +extern sa_resource_t sa_add_resource(sa_share_t, char *, int, int *); +extern int sa_remove_resource(sa_resource_t); +extern char *sa_get_resource_attr(sa_resource_t, char *); +extern int sa_set_resource_attr(sa_resource_t, char *, char *); +extern int sa_set_resource_description(sa_resource_t, char *); +extern char *sa_get_resource_description(sa_resource_t); +extern int sa_enable_resource(sa_resource_t, char *); +extern int sa_disable_resource(sa_resource_t, char *); +extern int sa_rename_resource(sa_resource_t, char *); +extern void sa_fix_resource_name(char *); + /* data structure free calls */ extern void sa_free_attr_string(char *); extern void sa_free_share_description(char *); @@ -179,6 +214,7 @@ extern int sa_update_property(sa_property_t, char *); extern int sa_remove_property(sa_property_t); extern int sa_commit_properties(sa_optionset_t, int); extern int sa_valid_property(void *, char *, sa_property_t); +extern int sa_is_persistent(void *); /* security control */ extern sa_security_t sa_get_security(sa_group_t, char *, char *); @@ -196,6 +232,7 @@ extern int sa_parse_legacy_options(sa_group_t, char *, char *); extern char *sa_proto_legacy_format(char *, sa_group_t, int); extern int sa_is_security(char *, char *); extern sa_protocol_properties_t sa_proto_get_properties(char *); +extern uint64_t sa_proto_get_featureset(char *); extern sa_property_t sa_get_protocol_property(sa_protocol_properties_t, char *); extern sa_property_t sa_get_next_protocol_property(sa_property_t); extern int sa_set_protocol_property(sa_property_t, char *); @@ -206,9 +243,12 @@ extern int sa_add_protocol_property(sa_protocol_properties_t, sa_property_t); extern int sa_proto_valid_prop(char *, sa_property_t, sa_optionset_t); extern int sa_proto_valid_space(char *, char *); extern char *sa_proto_space_alias(char *, char *); +extern int sa_proto_get_transients(sa_handle_t, char *); +extern int sa_proto_notify_resource(sa_resource_t, char *); +extern int sa_proto_change_notify(sa_share_t, char *); /* handle legacy (dfstab/sharetab) files */ -extern int sa_delete_legacy(sa_share_t); +extern int sa_delete_legacy(sa_share_t, char *); extern int sa_update_legacy(sa_share_t, char *); extern int sa_update_sharetab(sa_share_t, char *); extern int sa_delete_sharetab(char *, char *); diff --git a/usr/src/lib/libshare/common/libshare_impl.h b/usr/src/lib/libshare/common/libshare_impl.h index a454e3ef75..ab661e432f 100644 --- a/usr/src/lib/libshare/common/libshare_impl.h +++ b/usr/src/lib/libshare/common/libshare_impl.h @@ -72,6 +72,13 @@ struct sa_plugin_ops { char *(*sa_space_alias)(char *); int (*sa_update_legacy)(sa_share_t); int (*sa_delete_legacy)(sa_share_t); + int (*sa_change_notify)(sa_share_t); + int (*sa_enable_resource)(sa_resource_t); + int (*sa_disable_resource)(sa_resource_t); + uint64_t (*sa_features)(void); + int (*sa_get_transient_shares)(sa_handle_t); /* add transients */ + int (*sa_notify_resource)(sa_resource_t); + int (*sa_rename_resource)(sa_handle_t, sa_resource_t, char *); int (*sa_run_command)(int, int, char **); /* proto specific */ int (*sa_command_help)(); }; @@ -97,6 +104,8 @@ extern int sa_proto_unshare(sa_share_t, char *, char *); extern int sa_proto_valid_prop(char *, sa_property_t, sa_optionset_t); extern int sa_proto_security_prop(char *, char *); extern int sa_proto_legacy_opts(char *, sa_group_t, char *); +extern int sa_proto_share_resource(char *, sa_resource_t); +extern int sa_proto_unshare_resource(char *, sa_resource_t); /* internal utility functions */ extern sa_optionset_t sa_get_derived_optionset(sa_group_t, char *, int); @@ -121,7 +130,7 @@ extern void sa_emptyshare(struct share *sh); /* ZFS functions */ extern int sa_get_zfs_shares(sa_handle_t, char *); extern int sa_zfs_update(sa_share_t); -extern int sa_share_zfs(sa_share_t, char *, share_t *, void *, boolean_t); +extern int sa_share_zfs(sa_share_t, char *, share_t *, void *, zfs_share_op_t); extern int sa_sharetab_fill_zfs(sa_share_t share, struct share *sh, char *proto); @@ -131,6 +140,8 @@ extern void proto_plugin_fini(); extern int sa_proto_set_property(char *, sa_property_t); extern int sa_proto_delete_legacy(char *, sa_share_t); extern int sa_proto_update_legacy(char *, sa_share_t); +extern int sa_proto_rename_resource(sa_handle_t, char *, + sa_resource_t, char *); #define PL_TYPE_PROPERTY 0 #define PL_TYPE_SECURITY 1 diff --git a/usr/src/lib/libshare/common/libshare_zfs.c b/usr/src/lib/libshare/common/libshare_zfs.c index 1cfbcbe72a..e0ee84ef8a 100644 --- a/usr/src/lib/libshare/common/libshare_zfs.c +++ b/usr/src/lib/libshare/common/libshare_zfs.c @@ -36,7 +36,7 @@ #include <sys/mnttab.h> #include <sys/mntent.h> -extern sa_share_t _sa_add_share(sa_group_t, char *, int, int *); +extern sa_share_t _sa_add_share(sa_group_t, char *, int, int *, uint64_t); extern sa_group_t _sa_create_zfs_group(sa_group_t, char *); extern char *sa_fstype(char *); extern void set_node_attr(void *, char *, char *); @@ -109,7 +109,7 @@ sa_zfs_fini(sa_handle_impl_t impl_handle) /* * get_one_filesystem(zfs_handle_t, data) * - * an interator function called while iterating through the ZFS + * an iterator function called while iterating through the ZFS * root. It accumulates into an array of file system handles that can * be used to derive info about those file systems. * @@ -390,7 +390,7 @@ sa_zfs_is_shared(sa_handle_t sahandle, char *path) } /* - * find_or_create_group(groupname, proto, *err) + * find_or_create_group(handle, groupname, proto, *err) * * While walking the ZFS tree, we need to add shares to a defined * group. If the group doesn't exist, create it first, making sure it @@ -424,19 +424,8 @@ find_or_create_group(sa_handle_t handle, char *groupname, char *proto, int *err) if (group != NULL) { if (proto != NULL) { optionset = sa_get_optionset(group, proto); - if (optionset == NULL) { + if (optionset == NULL) optionset = sa_create_optionset(group, proto); - } else { - char **protolist; - int numprotos, i; - numprotos = sa_get_protocols(&protolist); - for (i = 0; i < numprotos; i++) { - optionset = sa_create_optionset(group, - protolist[i]); - } - if (protolist != NULL) - free(protolist); - } } } if (err != NULL) @@ -457,8 +446,8 @@ find_or_create_group(sa_handle_t handle, char *groupname, char *proto, int *err) */ static sa_group_t -find_or_create_zfs_subgroup(sa_handle_t handle, char *groupname, - char *optstring, int *err) +find_or_create_zfs_subgroup(sa_handle_t handle, char *groupname, char *proto, + char *optstring, int *err) { sa_group_t group = NULL; sa_group_t zfs; @@ -494,30 +483,58 @@ find_or_create_zfs_subgroup(sa_handle_t handle, char *groupname, options = strdup(optstring); if (options != NULL) { *err = sa_parse_legacy_options(group, - options, "nfs"); + options, proto); + + /* If no optionset, add one */ + if (sa_get_optionset(group, proto) == + NULL) + (void) sa_create_optionset( + group, proto); free(options); } else { *err = SA_NO_MEMORY; } } + } else if (proto != NULL && strcmp(proto, "smb") == 0) { + *err = SA_PROP_SHARE_ONLY; } } return (group); } /* + * zfs_construct_resource(share, name, base, dataset) + * + * Add a resource to the share using name as a template. If name == + * NULL, then construct a name based on the dataset value. + * name. + */ +static void +zfs_construct_resource(sa_share_t share, char *dataset) +{ + char buff[SA_MAX_RESOURCE_NAME + 1]; + int ret = SA_OK; + + (void) snprintf(buff, SA_MAX_RESOURCE_NAME, "%s", dataset); + sa_fix_resource_name(buff); + (void) sa_add_resource(share, buff, SA_SHARE_TRANSIENT, &ret); +} + +/* * zfs_inherited(handle, source, sourcestr) * - * handle case of inherited sharenfs. Pulled out of sa_get_zfs_shares + * handle case of inherited share{nfs,smb}. Pulled out of sa_get_zfs_shares * for readability. */ static int zfs_inherited(sa_handle_t handle, sa_share_t share, char *sourcestr, - char *shareopts, char *mountpoint) + char *shareopts, char *mountpoint, char *proto, char *dataset) { int doshopt = 0; int err = SA_OK; sa_group_t group; + sa_resource_t resource; + uint64_t features; /* * Need to find the "real" parent sub-group. It may not be @@ -525,11 +542,18 @@ zfs_inherited(sa_handle_t handle, sa_share_t share, char *sourcestr, * variable. The real parent not mounted can occur if * "canmount=off and sharenfs=on". */ - group = find_or_create_zfs_subgroup(handle, sourcestr, shareopts, - &doshopt); + group = find_or_create_zfs_subgroup(handle, sourcestr, proto, + shareopts, &doshopt); if (group != NULL) { - share = _sa_add_share(group, mountpoint, SA_SHARE_TRANSIENT, - &err); + /* + * We may need the first share for resource + * prototype. We only care about it if it has a + * resource that sets a prefix value. + */ + if (share == NULL) + share = _sa_add_share(group, mountpoint, + SA_SHARE_TRANSIENT, &err, + (uint64_t)SA_FEATURE_NONE); /* * some options may only be on shares. If the opt * string contains one of those, we put it just on the @@ -539,10 +563,27 @@ zfs_inherited(sa_handle_t handle, sa_share_t share, char *sourcestr, char *options; options = strdup(shareopts); if (options != NULL) { + set_node_attr(share, "dataset", dataset); err = sa_parse_legacy_options(share, options, - "nfs"); + proto); + set_node_attr(share, "dataset", NULL); free(options); } + if (sa_get_optionset(group, proto) == NULL) + (void) sa_create_optionset(group, proto); + } + features = sa_proto_get_featureset(proto); + if (share != NULL && features & SA_FEATURE_RESOURCE) { + /* + * We have a share and the protocol requires + * that at least one resource exist (probably + * SMB). We need to make sure that there is at + * least one. + */ + resource = sa_get_share_resource(share, NULL); + if (resource == NULL) { + zfs_construct_resource(share, dataset); + } } } else { err = SA_NO_MEMORY; @@ -557,45 +598,60 @@ zfs_inherited(sa_handle_t handle, sa_share_t share, char *sourcestr, * of sa_get_zfs_shares for readability. */ static int -zfs_notinherited(sa_group_t group, char *mountpoint, char *shareopts) +zfs_notinherited(sa_group_t group, sa_share_t share, char *mountpoint, + char *shareopts, char *proto, char *dataset) { int err = SA_OK; - sa_share_t share; - char *options; + sa_resource_t resource; + uint64_t features; set_node_attr(group, "zfs", "true"); - share = _sa_add_share(group, mountpoint, SA_SHARE_TRANSIENT, &err); + if (share == NULL) + share = _sa_add_share(group, mountpoint, SA_SHARE_TRANSIENT, + &err, (uint64_t)SA_FEATURE_NONE); if (err == SA_OK) { if (strcmp(shareopts, "on") == 0) - shareopts = "rw"; - - options = strdup(shareopts); - if (options != NULL) { - err = sa_parse_legacy_options(group, options, - "nfs"); - free(options); - } - if (err == SA_PROP_SHARE_ONLY) { - /* - * Same as above, some properties may - * only be on shares, but due to the - * ZFS sub-groups being artificial, we - * sometimes get this and have to deal - * with it. We do it by attempting to - * put it on the share. - */ + shareopts = ""; + if (shareopts != NULL) { + char *options; options = strdup(shareopts); if (options != NULL) { - err = sa_parse_legacy_options(share, - options, "nfs"); + err = sa_parse_legacy_options(group, options, + proto); free(options); } + if (err == SA_PROP_SHARE_ONLY) { + /* + * Same as above, some properties may + * only be on shares, but due to the + * ZFS sub-groups being artificial, we + * sometimes get this and have to deal + * with it. We do it by attempting to + * put it on the share. + */ + options = strdup(shareopts); + if (options != NULL) { + err = sa_parse_legacy_options(share, + options, proto); + free(options); + } + } + /* unmark the share's changed state */ + set_node_attr(share, "changed", NULL); + } + features = sa_proto_get_featureset(proto); + if (share != NULL && features & SA_FEATURE_RESOURCE) { + /* + * We have a share and the protocol requires + * that at least one resource exist (probably + * SMB). We need to make sure that there is at + * least one. + */ + resource = sa_get_share_resource(share, NULL); + if (resource == NULL) { + zfs_construct_resource(share, dataset); + } } - /* Mark as the defining node of the subgroup */ - set_node_attr(share, "subgroup", "true"); - - /* unmark the share's changed state */ - set_node_attr(share, "changed", NULL); } return (err); } @@ -619,6 +675,46 @@ zfs_grp_error(int err) } /* + * zfs_process_share(handle, share, mountpoint, proto, source, + * shareopts, sourcestr) + * + * Creates the subgroup, if necessary and adds shares and adds shares + * and properties. + */ +static int +zfs_process_share(sa_handle_t handle, sa_group_t group, sa_share_t share, + char *mountpoint, char *proto, zprop_source_t source, char *shareopts, + char *sourcestr, char *dataset) +{ + int err = SA_OK; + + if (source & ZPROP_SRC_INHERITED) { + err = zfs_inherited(handle, share, sourcestr, shareopts, + mountpoint, proto, dataset); + } else { + group = find_or_create_zfs_subgroup(handle, dataset, proto, + shareopts, &err); + if (group == NULL) { + static int err = 0; + /* + * there is a problem, but we can't do + * anything about it at this point so we issue + * a warning an move on. + */ + zfs_grp_error(err); + err = 1; + } + set_node_attr(group, "zfs", "true"); + /* + * Add share with local opts via zfs_notinherited. + */ + err = zfs_notinherited(group, share, mountpoint, shareopts, + proto, dataset); + } + return (err); +} + +/* * sa_get_zfs_shares(handle, groupname) * * Walk the mnttab for all zfs mounts and determine which are @@ -652,7 +748,7 @@ sa_get_zfs_shares(sa_handle_t handle, char *groupname) if (zfs_libhandle == NULL) return (SA_SYSTEM_ERR); - zfsgroup = find_or_create_group(handle, groupname, "nfs", &err); + zfsgroup = find_or_create_group(handle, groupname, NULL, &err); if (zfsgroup == NULL) return (legacy); @@ -666,6 +762,7 @@ sa_get_zfs_shares(sa_handle_t handle, char *groupname) group = zfsgroup; for (i = 0; i < count; i++) { char *dataset; + int foundnfs = 0; source = ZPROP_SRC_ALL; /* If no mountpoint, skip. */ @@ -694,8 +791,9 @@ sa_get_zfs_shares(sa_handle_t handle, char *groupname) ZFS_MAXPROPLEN, B_FALSE) == 0 && strcmp(shareopts, "off") != 0) { /* it is shared so add to list */ - share = sa_find_share(handle, mountpoint); err = SA_OK; + foundnfs = 1; + share = sa_find_share(handle, mountpoint); if (share != NULL) { /* * A zfs file system had been shared @@ -711,36 +809,36 @@ sa_get_zfs_shares(sa_handle_t handle, char *groupname) share = NULL; } if (err == SA_OK) { - if (source & ZPROP_SRC_INHERITED) { - err = zfs_inherited(handle, - share, sourcestr, - shareopts, mountpoint); - } else { - group = _sa_create_zfs_group( - zfsgroup, dataset); - if (group == NULL) { - static int err = 0; - /* - * there is a problem, - * but we can't do - * anything about it - * at this point so we - * issue a warning an - * move on. - */ - zfs_grp_error(err); - err = 1; - continue; - } - set_node_attr(group, "zfs", - "true"); - /* - * Add share with local opts via - * zfs_notinherited. - */ - err = zfs_notinherited(group, - mountpoint, shareopts); - } + err = zfs_process_share(handle, group, + share, mountpoint, "nfs", source, + shareopts, sourcestr, dataset); + } + } + if (zfs_prop_get(zlist[i], ZFS_PROP_SHARESMB, shareopts, + sizeof (shareopts), &source, sourcestr, + ZFS_MAXPROPLEN, B_FALSE) == 0 && + strcmp(shareopts, "off") != 0) { + /* it is shared so add to list */ + err = SA_OK; + share = sa_find_share(handle, mountpoint); + if (share != NULL && !foundnfs) { + /* + * A zfs file system had been shared + * through traditional methods + * (share/dfstab or added to a non-zfs + * group. Now it has been added to a + * ZFS group via the zfs + * command. Remove from previous + * config and setup with current + * options. + */ + err = sa_remove_share(share); + share = NULL; + } + if (err == SA_OK) { + err = zfs_process_share(handle, group, + share, mountpoint, "smb", source, + shareopts, sourcestr, dataset); } } } @@ -811,6 +909,122 @@ sa_zfs_set_sharenfs(sa_group_t group, char *path, int on) } /* + * add_resources(share, opt) + * + * Add resource properties to those in "opt". Resources are prefixed + * with name=resourcename. + */ +static char * +add_resources(sa_share_t share, char *opt) +{ + char *newopt = NULL; + char *propstr; + sa_resource_t resource; + + newopt = strdup(opt); + if (newopt == NULL) + return (newopt); + + for (resource = sa_get_share_resource(share, NULL); + resource != NULL; + resource = sa_get_next_resource(resource)) { + char *name; + size_t size; + + name = sa_get_resource_attr(resource, "name"); + if (name == NULL) { + free(newopt); + return (NULL); + } + size = strlen(name) + strlen(opt) + sizeof ("name=") + 1; + newopt = calloc(1, size); + if (newopt != NULL) + (void) snprintf(newopt, size, "%s,name=%s", opt, name); + free(opt); + opt = newopt; + propstr = sa_proto_legacy_format("smb", resource, 0); + if (propstr == NULL) { + free(opt); + return (NULL); + } + size = strlen(propstr) + strlen(opt) + 2; + newopt = calloc(1, size); + if (newopt != NULL) + (void) snprintf(newopt, size, "%s,%s", opt, propstr); + free(opt); + opt = newopt; + } + return (opt); +} + +/* + * sa_zfs_set_sharesmb(group, path, on) + * + * Update the "sharesmb" property on the path. If on is true, then set + * to the properties on the group or "on" if no properties are + * defined. Set to "off" if on is false. + */ + +int +sa_zfs_set_sharesmb(sa_group_t group, char *path, int on) +{ + int ret = SA_NOT_IMPLEMENTED; + char *command; + sa_share_t share; + + /* In case SMB not enabled */ + if (sa_get_optionset(group, "smb") == NULL) + return (SA_NOT_SUPPORTED); + + command = malloc(ZFS_MAXPROPLEN * 2); + if (command != NULL) { + char *opts = NULL; + char *dataset = NULL; + FILE *pfile; + sa_handle_impl_t impl_handle; + + if (on) { + char *newopt; + + share = sa_get_share(group, NULL); + opts = sa_proto_legacy_format("smb", share, 1); + if (opts != NULL && strlen(opts) == 0) { + free(opts); + opts = strdup("on"); + } + newopt = add_resources(opts, share); + free(opts); + opts = newopt; + } + + impl_handle = (sa_handle_impl_t)sa_find_group_handle(group); + assert(impl_handle != NULL); + if (impl_handle != NULL) + dataset = get_zfs_dataset(impl_handle, path, B_FALSE); + else + ret = SA_SYSTEM_ERR; + + if (dataset != NULL) { + (void) snprintf(command, ZFS_MAXPROPLEN * 2, + "echo %s set sharesmb=\"%s\" %s", COMMAND, + opts != NULL ? opts : "off", dataset); + pfile = popen(command, "r"); + if (pfile != NULL) { + ret = pclose(pfile); + if (ret != 0) + ret = SA_SYSTEM_ERR; + } + } + if (opts != NULL) + free(opts); + if (dataset != NULL) + free(dataset); + free(command); + } + return (ret); +} + +/* * sa_zfs_update(group) * * call back to ZFS to update the share if necessary. @@ -986,7 +1200,7 @@ sa_sharetab_fill_zfs(sa_share_t share, share_t *sh, char *proto) int sa_share_zfs(sa_share_t share, char *path, share_t *sh, - void *exportdata, boolean_t on) + void *exportdata, zfs_share_op_t operation) { libzfs_handle_t *libhandle; sa_group_t group; @@ -1057,7 +1271,7 @@ sa_share_zfs(sa_share_t share, char *path, share_t *sh, sh->sh_size += j; SMAX(i, j); err = zfs_deleg_share_nfs(libhandle, dataset, path, - exportdata, sh, i, on); + exportdata, sh, i, operation); libzfs_fini(libhandle); } free(dataset); diff --git a/usr/src/lib/libshare/common/libsharecore.c b/usr/src/lib/libshare/common/libsharecore.c index 503a424cc6..2e39594fc3 100644 --- a/usr/src/lib/libshare/common/libsharecore.c +++ b/usr/src/lib/libshare/common/libsharecore.c @@ -51,6 +51,7 @@ #include <sys/param.h> #include <signal.h> #include <libintl.h> +#include <dirent.h> #include <sharefs/share.h> #include "sharetab.h" @@ -88,7 +89,7 @@ extern char *get_token(char *); static void dfs_free_list(xfs_sharelist_t *); /* prototypes */ void getlegacyconfig(sa_handle_t, char *, xmlNodePtr *); -extern sa_share_t _sa_add_share(sa_group_t, char *, int, int *); +extern sa_share_t _sa_add_share(sa_group_t, char *, int, int *, uint64_t); extern sa_group_t _sa_create_group(sa_handle_impl_t, char *); static void outdfstab(FILE *, xfs_sharelist_t *); extern int _sa_remove_optionset(sa_optionset_t); @@ -563,13 +564,13 @@ sa_comment_line(char *line, char *err) } /* - * sa_delete_legacy(share) + * sa_delete_legacy(share, protocol) * * Delete the specified share from the legacy config file. */ int -sa_delete_legacy(sa_share_t share) +sa_delete_legacy(sa_share_t share, char *protocol) { FILE *dfstab; int err; @@ -580,39 +581,54 @@ sa_delete_legacy(sa_share_t share) sa_group_t parent; sigset_t old; + /* + * Protect against shares that don't have paths. This is not + * really an error at this point. + */ + path = sa_get_share_attr(share, "path"); + if (path == NULL) + return (ret); + dfstab = open_dfstab(SA_LEGACY_DFSTAB); if (dfstab != NULL) { (void) setvbuf(dfstab, NULL, _IOLBF, BUFSIZ * 8); sablocksigs(&old); - path = sa_get_share_attr(share, "path"); parent = sa_get_parent_group(share); if (parent != NULL) { (void) lockf(fileno(dfstab), F_LOCK, 0); list = getdfstab(dfstab); rewind(dfstab); - for (optionset = sa_get_optionset(parent, NULL); - optionset != NULL; - optionset = sa_get_next_optionset(optionset)) { - char *proto = sa_get_optionset_attr(optionset, - "type"); - if (list != NULL && proto != NULL) + if (protocol != NULL) { + if (list != NULL) list = remdfsentry(list, path, - proto); - if (proto == NULL) - ret = SA_NO_MEMORY; - /* - * May want to only do the dfstab if - * this call returns NOT IMPLEMENTED - * but it shouldn't hurt. - */ - if (ret == SA_OK) { - err = sa_proto_delete_legacy(proto, - share); - if (err != SA_NOT_IMPLEMENTED) - ret = err; + protocol); + } else { + for (optionset = sa_get_optionset(parent, NULL); + optionset != NULL; + optionset = + sa_get_next_optionset(optionset)) { + char *proto = sa_get_optionset_attr( + optionset, "type"); + + if (list != NULL && proto != NULL) + list = remdfsentry(list, path, + proto); + if (proto == NULL) + ret = SA_NO_MEMORY; + /* + * may want to only do the dfstab if + * this call returns NOT IMPLEMENTED + * but it shouldn't hurt. + */ + if (ret == SA_OK) { + err = sa_proto_delete_legacy( + proto, share); + if (err != SA_NOT_IMPLEMENTED) + ret = err; + } + if (proto != NULL) + sa_free_attr_string(proto); } - if (proto != NULL) - sa_free_attr_string(proto); } outdfstab(dfstab, list); if (list != NULL) @@ -623,13 +639,16 @@ sa_delete_legacy(sa_share_t share) (void) fsync(fileno(dfstab)); saunblocksigs(&old); (void) fclose(dfstab); - sa_free_attr_string(path); } else { if (errno == EACCES || errno == EPERM) ret = SA_NO_PERMISSION; else ret = SA_CONFIG_ERR; } + + if (path != NULL) + sa_free_attr_string(path); + return (ret); } @@ -639,7 +658,7 @@ sa_delete_legacy(sa_share_t share) * There is an assumption that dfstab will be the most common form of * legacy configuration file for shares, but not the only one. Because * of that, dfstab handling is done in the main code with calls to - * this function and protocol specific calls to deal with formating + * this function and protocol specific calls to deal with formatting * options into dfstab/share compatible syntax. Since not everything * will be dfstab, there is a provision for calling a protocol * specific plugin interface that allows the protocol plugin to do its @@ -655,10 +674,16 @@ sa_update_legacy(sa_share_t share, char *proto) char *path; sigset_t old; char *persist; + uint64_t features; ret = sa_proto_update_legacy(proto, share); if (ret != SA_NOT_IMPLEMENTED) return (ret); + + features = sa_proto_get_featureset(proto); + if (!(features & SA_FEATURE_DFSTAB)) + return (ret); + /* do the dfstab format */ persist = sa_get_share_attr(share, "type"); /* @@ -748,6 +773,21 @@ sa_is_share(void *object) } return (0); } +/* + * sa_is_resource(object) + * + * returns true of the object is of type "share". + */ + +int +sa_is_resource(void *object) +{ + if (object != NULL) { + if (strcmp((char *)((xmlNodePtr)object)->name, "resource") == 0) + return (1); + } + return (0); +} /* * _sa_remove_property(property) @@ -1392,6 +1432,7 @@ parse_sharetab(sa_handle_t handle) sa_group_t lgroup; char *groupname; int legacy = 0; + char shareopts[MAXNAMLEN]; list = get_share_list(&err); if (list == NULL) @@ -1410,7 +1451,9 @@ parse_sharetab(sa_handle_t handle) * share with no arguments. */ set_node_attr(share, "shared", "true"); - set_node_attr(share, "shareopts", tmplist->options); + (void) snprintf(shareopts, MAXNAMLEN, "shareopts-%s", + tmplist->fstype); + set_node_attr(share, shareopts, tmplist->options); continue; } @@ -1426,9 +1469,9 @@ parse_sharetab(sa_handle_t handle) *groupname++ = '\0'; group = sa_get_group(handle, groupname); if (group != NULL) { - share = _sa_add_share(group, - tmplist->path, - SA_SHARE_TRANSIENT, &err); + share = _sa_add_share(group, tmplist->path, + SA_SHARE_TRANSIENT, &err, + (uint64_t)SA_FEATURE_NONE); } else { /* * While this case shouldn't @@ -1447,7 +1490,7 @@ parse_sharetab(sa_handle_t handle) */ share = _sa_add_share(lgroup, tmplist->path, SA_SHARE_TRANSIENT, - &err); + &err, (uint64_t)SA_FEATURE_NONE); } } else { if (sa_zfs_is_shared(handle, tmplist->path)) { @@ -1472,11 +1515,12 @@ parse_sharetab(sa_handle_t handle) if (group != NULL) { share = _sa_add_share(group, tmplist->path, SA_SHARE_TRANSIENT, - &err); + &err, (uint64_t)SA_FEATURE_NONE); } } else { share = _sa_add_share(lgroup, tmplist->path, - SA_SHARE_TRANSIENT, &err); + SA_SHARE_TRANSIENT, &err, + (uint64_t)SA_FEATURE_NONE); } } if (share == NULL) @@ -1517,12 +1561,22 @@ int gettransients(sa_handle_impl_t ihandle, xmlNodePtr *root) { int legacy = 0; + int numproto; + char **protocols = NULL; + int i; if (root != NULL) { if (*root == NULL) *root = xmlNewNode(NULL, (xmlChar *)"sharecfg"); - if (*root != NULL) + if (*root != NULL) { legacy = parse_sharetab(ihandle); + numproto = sa_get_protocols(&protocols); + for (i = 0; i < numproto; i++) + legacy |= sa_proto_get_transients( + (sa_handle_t)ihandle, protocols[i]); + if (protocols != NULL) + free(protocols); + } } return (legacy); } @@ -1630,7 +1684,7 @@ sa_free_fstype(char *type) * Work backward to the top of the share object tree and start * copying protocol specific optionsets into a newly created * optionset that doesn't have a parent (it will be freed - * later). This provides for the property inheritence model. That + * later). This provides for the property inheritance model. That * is, properties closer to the share take precedence over group * level. This also provides for groups of groups in the future. */ @@ -1719,7 +1773,7 @@ sa_free_derived_optionset(sa_optionset_t optionset) * Find all the security types set for this object. This is * preliminary to getting a derived security set. The return value is an * optionset containg properties which are the sectype values found by - * walking up the XML document struture. The returned optionset + * walking up the XML document structure. The returned optionset * is a derived optionset. * * If hier is 0, only look at object. If non-zero, walk up the tree. @@ -1885,6 +1939,19 @@ sa_fillshare(sa_share_t share, char *proto, struct share *sh) sa_group_t group; char *buff; char *zfs; + sa_resource_t resource; + char *rsrcname = NULL; + char *defprop; + + /* + * We only want to deal with the path level shares for the + * sharetab file. If a resource, get the parent. + */ + if (sa_is_resource(share)) { + resource = (sa_resource_t)share; + share = sa_get_resource_parent(resource); + rsrcname = sa_get_resource_attr(resource, "name"); + } group = sa_get_parent_group(share); if (group != NULL) { @@ -1912,41 +1979,48 @@ sa_fillshare(sa_share_t share, char *proto, struct share *sh) sa_free_attr_string(value); } - value = sa_get_share_attr(share, "resource"); - if (value != NULL || groupname != NULL) { + if (rsrcname != NULL || groupname != NULL) { int len = 0; - if (value != NULL) - len += strlen(value); + if (rsrcname != NULL) + len += strlen(rsrcname); if (groupname != NULL) len += strlen(groupname); len += 3; /* worst case */ buff = malloc(len); (void) snprintf(buff, len, "%s%s%s", - (value != NULL && strlen(value) > 0) ? value : "-", + (rsrcname != NULL && + strlen(rsrcname) > 0) ? rsrcname : "-", groupname != NULL ? "@" : "", groupname != NULL ? groupname : ""); sh->sh_res = buff; - if (value != NULL) - sa_free_attr_string(value); - if (groupname != NULL) { + if (rsrcname != NULL) + sa_free_attr_string(rsrcname); + if (groupname != NULL) sa_free_attr_string(groupname); - groupname = NULL; - } } else { sh->sh_res = strdup("-"); } + /* + * Get correct default prop string. NFS uses "rw", others use + * "". + */ + if (strcmp(proto, "nfs") != 0) + defprop = "\"\""; + else + defprop = "rw"; + sh->sh_fstype = strdup(proto); value = sa_proto_legacy_format(proto, share, 1); if (value != NULL) { if (strlen(value) > 0) sh->sh_opts = strdup(value); else - sh->sh_opts = strdup("rw"); + sh->sh_opts = strdup(defprop); free(value); } else { - sh->sh_opts = strdup("rw"); + sh->sh_opts = strdup(defprop); } value = sa_get_share_description(share); @@ -2041,3 +2115,67 @@ sa_delete_sharetab(char *path, char *proto) return (ret); } +/* + * sa_fix_resource_name(path) + * + * change all illegal characters to something else. For now, all get + * converted to '_' and the leading '/' is stripped off. This is used + * to construct an resource name (SMB share name) that is valid. + * Caller must pass a valid path. + */ +void +sa_fix_resource_name(char *path) +{ + char *cp; + size_t len; + + assert(path != NULL); + + /* make sure we are appropriate length */ + cp = path; + if (*cp == '/') + cp++; /* skip leading slash */ + while (cp != NULL && strlen(cp) > SA_MAX_RESOURCE_NAME) { + cp = strchr(cp, '/'); + if (cp != NULL) + cp++; + } + /* two cases - cp == NULL and cp is substring of path */ + if (cp == NULL) { + /* just take last SA_MAX_RESOURCE_NAME chars */ + len = 1 + strlen(path) - SA_MAX_RESOURCE_NAME; + (void) memmove(path, path + len, SA_MAX_RESOURCE_NAME); + path[SA_MAX_RESOURCE_NAME] = '\0'; + } else { + len = strlen(cp) + 1; + (void) memmove(path, cp, len); + } + + /* + * Don't want any of the characters that are not allowed + * in an SMB share name. Replace them with '_'. + */ + while (*path) { + switch (*path) { + case '/': + case '"': + case '\\': + case '[': + case ']': + case ':': + case '|': + case '<': + case '>': + case '+': + case ';': + case ',': + case '?': + case '*': + case '=': + case '\t': + *path = '_'; + break; + } + path++; + } +} diff --git a/usr/src/lib/libshare/common/mapfile-vers b/usr/src/lib/libshare/common/mapfile-vers index 317623b3b4..f7dfde5730 100644 --- a/usr/src/lib/libshare/common/mapfile-vers +++ b/usr/src/lib/libshare/common/mapfile-vers @@ -106,9 +106,28 @@ SUNWprivate { sa_delete_legacy; sa_free_derived_security; sa_enable_share; + sa_enable_resource; sa_create_group; sa_valid_protocol; sa_find_group_handle; + sa_add_resource; + sa_remove_resource; + sa_rename_resource; + sa_get_resource; + sa_get_next_resource; + sa_get_resource_attr; + sa_set_resource_attr; + sa_get_share_resource; + sa_get_resource_parent; + sa_find_resource; + sa_proto_change_notify; + sa_proto_notify_resource; + sa_disable_resource; + sa_proto_get_featureset; + sa_is_persistent; + sa_set_resource_description; + sa_get_resource_description; + sa_fix_resource_name; local: *; }; diff --git a/usr/src/lib/libshare/common/plugin.c b/usr/src/lib/libshare/common/plugin.c index 4079e24ff7..08856a8951 100644 --- a/usr/src/lib/libshare/common/plugin.c +++ b/usr/src/lib/libshare/common/plugin.c @@ -64,7 +64,7 @@ void proto_plugin_fini(); * /usr/lib/fs/nfs/libshare_nfs.so. The protocol specific directory * would have a modules with name libshare_<proto>.so. If one is * found, initialize it and add to the internal list of - * protocols. These are used for protocol specifici operations. + * protocols. These are used for protocol specific operations. */ int @@ -242,9 +242,9 @@ sa_proto_share(char *proto, sa_share_t share) } /* - * sa_proto_unshare(proto, path) + * sa_proto_unshare(proto, share) * - * Deactivate (unshare) the path for this protocol. + * Deactivate (unshare) the share for this protocol. */ int @@ -259,6 +259,52 @@ sa_proto_unshare(sa_share_t share, char *proto, char *path) } /* + * sa_proto_share_resource(char *proto, sa_resource_t resource) + * + * For protocols that actually enable at the resource level, do the + * protocol specific resource enable. If it doesn't, return an error. + * Note that the resource functions are optional so can return + * SA_NOT_SUPPORTED. + */ + +int +sa_proto_share_resource(char *proto, sa_resource_t resource) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_INVALID_PROTOCOL; + + if (ops != NULL) { + if (ops->sa_enable_resource != NULL) + ret = ops->sa_enable_resource(resource); + else + ret = SA_NOT_SUPPORTED; + } + return (ret); +} + +/* + * sa_proto_unshare_resource(char *proto, sa_resource_t resource) + * + * For protocols that actually disable at the resource level, do the + * protocol specific resource disable. If it doesn't, return an error. + */ + +int +sa_proto_unshare_resource(char *proto, sa_resource_t resource) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_INVALID_PROTOCOL; + + if (ops != NULL) { + if (ops->sa_disable_resource != NULL) + ret = ops->sa_disable_resource(resource); + else + ret = SA_NOT_SUPPORTED; + } + return (ret); +} + +/* * sa_proto_valid_prop(proto, prop, opt) * * Check to see if the specified prop is valid for this protocol. @@ -395,7 +441,7 @@ sa_proto_get_properties(char *proto) /* * sa_proto_set_property(proto, prop) * - * Update the protocol specifiec property. + * Update the protocol specific property. */ int @@ -467,16 +513,127 @@ int sa_proto_delete_legacy(char *proto, sa_share_t share) { struct sa_plugin_ops *ops = find_protocol(proto); - int ret = SA_OK; + int ret = SA_NOT_IMPLEMENTED; if (ops != NULL) { if (ops->sa_delete_legacy != NULL) ret = ops->sa_delete_legacy(share); - } else { - if (proto != NULL) - ret = SA_NOT_IMPLEMENTED; - else + } else if (proto == NULL) { + ret = SA_INVALID_PROTOCOL; + } + return (ret); +} + +/* + * sa_proto_change_notify(share, char *protocol) + * + * Notify the protocol that a change has been made to the share + */ + +int +sa_proto_change_notify(sa_share_t share, char *proto) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_NOT_IMPLEMENTED; + + if (ops != NULL) { + if (ops->sa_change_notify != NULL) + ret = ops->sa_change_notify(share); + } else if (proto == NULL) { ret = SA_INVALID_PROTOCOL; } return (ret); } + +/* + * sa_proto_notify_resource(resource, char *protocol) + * + * Notify the protocol that a change has been made to the share + */ + +int +sa_proto_notify_resource(sa_resource_t resource, char *proto) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_NOT_IMPLEMENTED; + + if (ops != NULL) { + if (ops->sa_notify_resource != NULL) + ret = ops->sa_notify_resource(resource); + } else if (proto == NULL) { + ret = SA_INVALID_PROTOCOL; + } + return (ret); +} + +/* + * sa_proto_get_featureset(protocol) + * + * Get bitmask of defined features of the protocol. These are + * primarily things like SA_FEATURE_RESOURCE (shares are by resource + * name rather than path) and other operational features that affect + * behavior. + */ + +uint64_t +sa_proto_get_featureset(char *proto) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + uint64_t ret = 0; + + if (ops != NULL) { + if (ops->sa_features != NULL) + ret = ops->sa_features(); + } + /* if not implemented, zero is valid */ + return (ret); +} + +/* + * sa_proto_get_transients(sa_handle_t) + * + * Called to get any protocol specific transient shares. NFS doesn't + * use this since the info is in sharetab which is processed as a + * common transient store. + * + * The protocol plugin should verify that the share isn't in the + * repository and then add it as a transient. + * + * Not having an entry is not a problem. It returns 0 in that case. + */ + +int +sa_proto_get_transients(sa_handle_t handle, char *proto) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = 0; + + if (ops != NULL) { + if (ops->sa_get_transient_shares != NULL) + ret = ops->sa_get_transient_shares(handle); + } + return (ret); +} + +/* + * sa_proto_rename_resource(sa_handle_t, proto, sa_resource_t, newname) + * + * Protocols may need to know when a resource has changed names in + * order to notify clients. This must be done "before" the name in the + * resource has been changed. Not being implemented is not a problem. + */ + +int +sa_proto_rename_resource(sa_handle_t handle, char *proto, + sa_resource_t resource, char *newname) +{ + struct sa_plugin_ops *ops = find_protocol(proto); + int ret = SA_OK; + + if (ops != NULL) { + if (ops->sa_rename_resource != NULL) + ret = ops->sa_rename_resource(handle, resource, + newname); + } + return (ret); +} diff --git a/usr/src/lib/libshare/common/scfutil.c b/usr/src/lib/libshare/common/scfutil.c index 05421d605b..344d3729bb 100644 --- a/usr/src/lib/libshare/common/scfutil.c +++ b/usr/src/lib/libshare/common/scfutil.c @@ -35,6 +35,7 @@ #include "libshare_impl.h" #include "scfutil.h" #include <string.h> +#include <ctype.h> #include <errno.h> #include <uuid/uuid.h> #include <sys/param.h> @@ -396,7 +397,8 @@ out: static char *share_attr[] = { "path", "id", - "resource", + "drive-letter", + "exclude", NULL, }; @@ -411,6 +413,52 @@ is_share_attr(char *name) } /* + * _sa_make_resource(node, valuestr) + * + * Make a resource node on the share node. The valusestr will either + * be old format (SMF acceptable string) or new format (pretty much an + * arbitrary string with "nnn:" prefixing in order to persist + * mapping). The input valuestr will get modified in place. This is + * only used in SMF repository parsing. A possible third field will be + * a "description" string. + */ + +static void +_sa_make_resource(xmlNodePtr node, char *valuestr) +{ + char *idx; + char *name; + char *description = NULL; + + idx = valuestr; + name = strchr(valuestr, ':'); + if (name == NULL) { + /* this is old form so give an index of "0" */ + idx = "0"; + name = valuestr; + } else { + /* NUL the ':' and move past it */ + *name++ = '\0'; + /* There could also be a description string */ + description = strchr(name, ':'); + if (description != NULL) + *description++ = '\0'; + } + node = xmlNewChild(node, NULL, (xmlChar *)"resource", NULL); + if (node != NULL) { + xmlSetProp(node, (xmlChar *)"name", (xmlChar *)name); + xmlSetProp(node, (xmlChar *)"id", (xmlChar *)idx); + /* SMF values are always persistent */ + xmlSetProp(node, (xmlChar *)"type", (xmlChar *)"persist"); + if (description != NULL && strlen(description) > 0) { + (void) xmlNewChild(node, NULL, (xmlChar *)"description", + (xmlChar *)description); + } + } +} + + +/* * sa_share_from_pgroup * * Extract the share definition from the share property group. We do @@ -490,34 +538,70 @@ sa_share_from_pgroup(xmlNodePtr root, scfutilhandle_t *handle, vallen) >= 0) { ret = SA_OK; } + } else if (strcmp(name, "resource") == 0) { + ret = SA_OK; } } - if (ret == SA_OK) { + if (ret != SA_OK) + continue; + /* + * Check that we have the "path" property in + * name. The string in name will always be nul + * terminated if scf_property_get_name() + * succeeded. + */ + if (strcmp(name, "path") == 0) + have_path = 1; + if (is_share_attr(name)) { /* - * Check that we have the "path" property in - * name. The string in name will always be nul - * terminated if scf_property_get_name() - * succeeded. + * If a share attr, then simple - + * usually path and id name */ - if (strcmp(name, "path") == 0) - have_path = 1; - if (is_share_attr(name)) { - /* - * If a share attr, then simple - - * usually path and resource name - */ - xmlSetProp(node, (xmlChar *)name, - (xmlChar *)valuestr); - } else { - if (strcmp(name, "description") == 0) { - /* We have a description node */ - xmlNodePtr desc; - desc = xmlNewChild(node, NULL, - (xmlChar *)"description", NULL); - if (desc != NULL) - xmlNodeSetContent(desc, - (xmlChar *)valuestr); + xmlSetProp(node, (xmlChar *)name, + (xmlChar *)valuestr); + } else if (strcmp(name, "resource") == 0) { + /* + * Resource names handled differently since + * there can be multiple on each share. The + * "resource" id must be preserved since this + * will be used by some protocols in mapping + * "property spaces" to names and is always + * used to create SMF property groups specific + * to resources. CIFS needs this. The first + * value is present so add and then loop for + * any additional. Since this is new and + * previous values may exist, handle + * conversions. + */ + scf_iter_t *viter; + viter = scf_iter_create(handle->handle); + if (viter != NULL && + scf_iter_property_values(viter, prop) == 0) { + while (scf_iter_next_value(viter, value) > 0) { + /* Have a value so process it */ + if (scf_value_get_ustring(value, + valuestr, vallen) >= 0) { + /* have a ustring */ + _sa_make_resource(node, + valuestr); + } else if (scf_value_get_astring(value, + valuestr, vallen) >= 0) { + /* have an astring */ + _sa_make_resource(node, + valuestr); + } } + scf_iter_destroy(viter); + } + } else { + if (strcmp(name, "description") == 0) { + /* We have a description node */ + xmlNodePtr desc; + desc = xmlNewChild(node, NULL, + (xmlChar *)"description", NULL); + if (desc != NULL) + xmlNodeSetContent(desc, + (xmlChar *)valuestr); } } } @@ -583,7 +667,34 @@ find_share_by_id(sa_handle_t handle, char *shareid) } /* - * sa_share_props_from_pgroup(root, handle, pg, id) + * find_resource_by_index(share, index) + * + * Search the resource records on the share for the id index. + */ +static sa_resource_t +find_resource_by_index(sa_share_t share, char *index) +{ + sa_resource_t resource; + sa_resource_t found = NULL; + char *id; + + for (resource = sa_get_share_resource(share, NULL); + resource != NULL && found == NULL; + resource = sa_get_next_resource(resource)) { + id = (char *)xmlGetProp((xmlNodePtr)resource, (xmlChar *)"id"); + if (id != NULL) { + if (strcmp(id, index) == 0) { + /* found it so save in "found" */ + found = resource; + } + sa_free_attr_string(id); + } + } + return (found); +} + +/* + * sa_share_props_from_pgroup(root, handle, pg, id, sahandle) * * Extract share properties from the SMF property group. More sanity * checks are done and the share object is created. We ignore some @@ -639,6 +750,7 @@ sa_share_props_from_pgroup(xmlNodePtr root, scfutilhandle_t *handle, /* not a valid proto (null) */ return (ret); } + sectype = strchr(proto, '_'); if (sectype != NULL) *sectype++ = '\0'; @@ -664,10 +776,35 @@ sa_share_props_from_pgroup(xmlNodePtr root, scfutilhandle_t *handle, if (sectype == NULL) node = xmlNewChild(root, NULL, (xmlChar *)"optionset", NULL); else { - node = xmlNewChild(root, NULL, (xmlChar *)"security", NULL); - if (node != NULL) - xmlSetProp(node, (xmlChar *)"sectype", - (xmlChar *)sectype); + if (isdigit((int)*sectype)) { + sa_resource_t resource; + /* + * If sectype[0] is a digit, then it is an index into + * the resource names. We need to find a resource + * record and then get the properties into an + * optionset. The optionset becomes the "node" and the + * rest is hung off of the share. + */ + resource = find_resource_by_index(share, sectype); + if (resource != NULL) { + node = xmlNewChild(resource, NULL, + (xmlChar *)"optionset", NULL); + } else { + /* this shouldn't happen */ + ret = SA_SYSTEM_ERR; + } + } else { + /* + * If not a digit, then it is a security type + * (alternate option space). Security types start with + * an alphabetic. + */ + node = xmlNewChild(root, NULL, (xmlChar *)"security", + NULL); + if (node != NULL) + xmlSetProp(node, (xmlChar *)"sectype", + (xmlChar *)sectype); + } } if (node == NULL) { ret = SA_NO_MEMORY; @@ -685,7 +822,7 @@ sa_share_props_from_pgroup(xmlNodePtr root, scfutilhandle_t *handle, if (iter == NULL || value == NULL || prop == NULL || name == NULL) goto out; - /* Iterate over the share pg properties */ + /* iterate over the share pg properties */ if (scf_iter_pg_properties(iter, pg) == 0) { while (scf_iter_next_property(iter, prop) > 0) { ret = SA_SYSTEM_ERR; /* assume the worst */ @@ -897,7 +1034,7 @@ out: * sa_extract_defaults(root, handle, instance) * * Local function to find the default properties that live in the - * default instance's "operation" proprerty group. + * default instance's "operation" property group. */ static void @@ -946,7 +1083,7 @@ out: /* - * sa_get_config(handle, root, doc, sahandlec) + * sa_get_config(handle, root, doc, sahandle) * * Walk the SMF repository for /network/shares/group and find all the * instances. These become group names. Then add the XML structure @@ -1276,6 +1413,147 @@ sa_set_property(scfutilhandle_t *handle, char *propname, char *valstr) } /* + * check_resource(share) + * + * Check to see if share has any persistent resources. We don't want + * to save if they are all transient. + */ +static int +check_resource(sa_share_t share) +{ + sa_resource_t resource; + int ret = B_FALSE; + + for (resource = sa_get_share_resource(share, NULL); + resource != NULL && ret == B_FALSE; + resource = sa_get_next_resource(resource)) { + char *type; + type = sa_get_resource_attr(resource, "type"); + if (type != NULL) { + if (strcmp(type, "transient") != 0) { + ret = B_TRUE; + } + sa_free_attr_string(type); + } + } + return (ret); +} + +/* + * sa_set_resource_property(handle, prop, value) + * + * set a property transaction entry into the pending SMF + * transaction. We don't want to include any transient resources + */ + +static int +sa_set_resource_property(scfutilhandle_t *handle, sa_share_t share) +{ + int ret = SA_OK; + scf_value_t *value; + scf_transaction_entry_t *entry; + sa_resource_t resource; + char *valstr; + char *idstr; + char *description; + char *propstr = NULL; + size_t strsize; + + /* don't bother if no persistent resources */ + if (check_resource(share) == B_FALSE) + return (ret); + + /* + * properties must be set in transactions and don't take + * effect until the transaction has been ended/committed. + */ + entry = scf_entry_create(handle->handle); + if (entry == NULL) + return (SA_SYSTEM_ERR); + + if (scf_transaction_property_change(handle->trans, entry, + "resource", SCF_TYPE_ASTRING) != 0 && + scf_transaction_property_new(handle->trans, entry, + "resource", SCF_TYPE_ASTRING) != 0) { + scf_entry_destroy(entry); + return (SA_SYSTEM_ERR); + + } + for (resource = sa_get_share_resource(share, NULL); + resource != NULL; + resource = sa_get_next_resource(resource)) { + value = scf_value_create(handle->handle); + if (value == NULL) { + ret = SA_NO_MEMORY; + break; + } + /* Get size of complete string */ + valstr = sa_get_resource_attr(resource, "name"); + idstr = sa_get_resource_attr(resource, "id"); + description = sa_get_resource_description(resource); + strsize = (valstr != NULL) ? strlen(valstr) : 0; + strsize += (idstr != NULL) ? strlen(idstr) : 0; + strsize += (description != NULL) ? strlen(description) : 0; + if (strsize > 0) { + strsize += 3; /* add nul and ':' */ + propstr = (char *)malloc(strsize); + if (propstr == NULL) { + scf_value_destroy(value); + ret = SA_NO_MEMORY; + goto err; + } + if (idstr == NULL) + (void) snprintf(propstr, strsize, "%s", + valstr ? valstr : ""); + else + (void) snprintf(propstr, strsize, "%s:%s:%s", + idstr ? idstr : "", valstr ? valstr : "", + description ? description : ""); + if (scf_value_set_astring(value, propstr) != 0) { + ret = SA_SYSTEM_ERR; + free(propstr); + scf_value_destroy(value); + break; + } + if (scf_entry_add_value(entry, value) != 0) { + ret = SA_SYSTEM_ERR; + free(propstr); + scf_value_destroy(value); + break; + } + /* the value is in the transaction */ + value = NULL; + free(propstr); + } +err: + if (valstr != NULL) + sa_free_attr_string(valstr); + if (idstr != NULL) + sa_free_attr_string(idstr); + if (description != NULL) + sa_free_share_description(description); + } + /* the entry is in the transaction */ + entry = NULL; + + if (ret == SA_SYSTEM_ERR) { + switch (scf_error()) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SA_NO_PERMISSION; + break; + } + } + /* + * cleanup if there were any errors that didn't leave + * these values where they would be cleaned up later. + */ + if (entry != NULL) + scf_entry_destroy(entry); + + return (ret); +} + +/* * sa_commit_share(handle, group, share) * * Commit this share to the repository. @@ -1288,7 +1566,6 @@ sa_commit_share(scfutilhandle_t *handle, sa_group_t group, sa_share_t share) int ret = SA_OK; char *groupname; char *name; - char *resource; char *description; char *sharename; ssize_t proplen; @@ -1371,15 +1648,35 @@ sa_commit_share(scfutilhandle_t *handle, sa_group_t group, sa_share_t share) } } if (ret == SA_OK) { - resource = sa_get_share_attr(share, - "resource"); - if (resource != NULL) { + name = sa_get_share_attr(share, "drive-letter"); + if (name != NULL) { + /* A drive letter may exist for SMB */ + ret = sa_set_property(handle, + "drive-letter", name); + sa_free_attr_string(name); + } + } + if (ret == SA_OK) { + name = sa_get_share_attr(share, "exclude"); + if (name != NULL) { + /* + * In special cases need to + * exclude proto enable. + */ ret = sa_set_property(handle, - "resource", resource); - sa_free_attr_string(resource); + "exclude", name); + sa_free_attr_string(name); } } if (ret == SA_OK) { + /* + * If there are resource names, bundle them up + * and save appropriately. + */ + ret = sa_set_resource_property(handle, share); + } + + if (ret == SA_OK) { description = sa_get_share_description(share); if (description != NULL) { ret = sa_set_property(handle, @@ -1414,6 +1711,49 @@ sa_commit_share(scfutilhandle_t *handle, sa_group_t group, sa_share_t share) } /* + * remove_resources(handle, share, shareid) + * + * If the share has resources, remove all of them and their + * optionsets. + */ +static int +remove_resources(scfutilhandle_t *handle, sa_share_t share, char *shareid) +{ + sa_resource_t resource; + sa_optionset_t opt; + char *proto; + char *id; + ssize_t proplen; + char *propstring; + int ret = SA_OK; + + proplen = get_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); + propstring = malloc(proplen); + if (propstring == NULL) + return (SA_NO_MEMORY); + + for (resource = sa_get_share_resource(share, NULL); + resource != NULL; resource = sa_get_next_resource(resource)) { + id = sa_get_resource_attr(resource, "id"); + if (id == NULL) + continue; + for (opt = sa_get_optionset(resource, NULL); + opt != NULL; opt = sa_get_next_optionset(resource)) { + proto = sa_get_optionset_attr(opt, "type"); + if (proto != NULL) { + (void) snprintf(propstring, proplen, + "%s_%s_%s", shareid, proto, id); + ret = sa_delete_pgroup(handle, propstring); + sa_free_attr_string(proto); + } + } + sa_free_attr_string(id); + } + free(propstring); + return (ret); +} + +/* * sa_delete_share(handle, group, share) * * Remove the specified share from the group (and service instance). @@ -1444,6 +1784,8 @@ sa_delete_share(scfutilhandle_t *handle, sa_group_t group, sa_share_t share) } ret = sa_get_instance(handle, groupname); if (ret == SA_OK) { + /* If a share has resources, remove them */ + ret = remove_resources(handle, share, shareid); /* If a share has properties, remove them */ ret = sa_delete_pgroup(handle, shareid); for (opt = sa_get_optionset(share, NULL); diff --git a/usr/src/lib/libshare/nfs/libshare_nfs.c b/usr/src/lib/libshare/nfs/libshare_nfs.c index a7426ab841..0a3175de70 100644 --- a/usr/src/lib/libshare/nfs/libshare_nfs.c +++ b/usr/src/lib/libshare/nfs/libshare_nfs.c @@ -73,6 +73,7 @@ static int nfs_set_proto_prop(sa_property_t); static sa_protocol_properties_t nfs_get_proto_set(); static char *nfs_get_status(); static char *nfs_space_alias(char *); +static uint64_t nfs_features(); /* * ops vector that provides the protocol specific info and operations @@ -95,7 +96,14 @@ struct sa_plugin_ops sa_plugin_ops = { nfs_get_proto_set, nfs_get_status, nfs_space_alias, - NULL, + NULL, /* update_legacy */ + NULL, /* delete_legacy */ + NULL, /* change_notify */ + NULL, /* enable_resource */ + NULL, /* disable_resource */ + nfs_features, + NULL, /* transient shares */ + NULL, /* notify resource */ NULL }; @@ -543,13 +551,16 @@ nfs_parse_legacy_options(sa_group_t group, char *options) optionset = sa_create_optionset(group, "nfs"); } else { /* - * have an existing optionset so we need to compare - * options in order to detect errors. For now, we - * assume that the first optionset is the correct one - * and the others will be the same. This needs to be - * fixed before the final code is ready. + * Have an existing optionset . Ideally, we would need + * to compare options in order to detect errors. For + * now, we assume that the first optionset is the + * correct one and the others will be the same. An + * empty optionset is the same as no optionset so we + * don't want to exit in that case. Getting an empty + * optionset can occur with ZFS property checking. */ - return (ret); + if (sa_get_property(optionset, NULL) != NULL) + return (ret); } if (strcmp(options, SHOPT_RW) == 0) { @@ -839,7 +850,7 @@ fill_export_from_optionset(struct exportdata *export, sa_optionset_t optionset) /* * since options may be set/reset multiple times, always do an * explicit set or clear of the option. This allows defaults - * to be set and then the protocol specifici to override. + * to be set and then the protocol specific to override. */ name = sa_get_property_attr(option, "type"); @@ -1804,7 +1815,8 @@ nfs_enable_share(sa_share_t share) ea.uex = &export; sa_sharetab_fill_zfs(share, &sh, "nfs"); - err = sa_share_zfs(share, path, &sh, &ea, B_TRUE); + err = sa_share_zfs(share, path, &sh, + &ea, ZFS_SHARE_NFS); sa_emptyshare(&sh); } } else { @@ -1909,7 +1921,8 @@ nfs_disable_share(sa_share_t share, char *path) sh.sh_path = path; sh.sh_fstype = "nfs"; - err = sa_share_zfs(share, path, &sh, &ea, B_FALSE); + err = sa_share_zfs(share, path, &sh, + &ea, ZFS_UNSHARE_NFS); } else err = exportfs(path, NULL); if (err < 0) { @@ -2802,7 +2815,7 @@ nfs_minmax_check(int index, int value) /* * nfs_validate_proto_prop(index, name, value) * - * Verify that the property specifed by name can take the new + * Verify that the property specified by name can take the new * value. This is a sanity check to prevent bad values getting into * the default files. All values need to be checked against what is * allowed by their defined type. If a type isn't explicitly defined @@ -2967,3 +2980,15 @@ nfs_space_alias(char *space) } return (strdup(name)); } + +/* + * nfs_features() + * + * Return a mask of the features required. + */ + +static uint64_t +nfs_features() +{ + return ((uint64_t)SA_FEATURE_DFSTAB); +} diff --git a/usr/src/lib/libshare/smb/Makefile b/usr/src/lib/libshare/smb/Makefile new file mode 100644 index 0000000000..d5f3df5233 --- /dev/null +++ b/usr/src/lib/libshare/smb/Makefile @@ -0,0 +1,51 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../../Makefile.lib + +SUBDIRS = $(MACH) +$(BUILD64)SUBDIRS += $(MACH64) + +MSGFILES = libshare_smb.c + +all := TARGET= all +clean := TARGET= clean +clobber := TARGET= clobber +install := TARGET= install +lint := TARGET= lint + +.KEEP_STATE: + +all clean clobber install lint: $(SUBDIRS) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include ../../Makefile.targ +include ../../../Makefile.msg.targ diff --git a/usr/src/lib/libshare/smb/Makefile.com b/usr/src/lib/libshare/smb/Makefile.com new file mode 100644 index 0000000000..997f47daef --- /dev/null +++ b/usr/src/lib/libshare/smb/Makefile.com @@ -0,0 +1,88 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +LIBRARY = libshare_smb.a +VERS = .1 +SMBMLSVC_DIR = $(SRC)/lib/smbsrv/libmlsvc/common +SMBBASE_DIR = $(SRC)/lib/smbsrv/libsmb/common +SMBCOMMON_DIR = $(SRC)/common/smbsrv + +LIBOBJS = libshare_smb.o smb_share_doorclnt.o +SMBCOMMON_OBJ = smb_share_door_decode.o smb_common_door_decode.o +SMBBASE_OBJ = smb_cfg.o smb_scfutil.o smb_door_client.o +SMBMLSVC_OBJ = smb_share_util.o +OBJECTS = $(LIBOBJS) $(SMBCOMMON_OBJ) $(SMBBASE_OBJ) $(SMBMLSVC_OBJ) + +include ../../../Makefile.lib + +ROOTLIBDIR = $(ROOT)/usr/lib/fs/smb +ROOTLIBDIR64 = $(ROOT)/usr/lib/fs/smb/$(MACH64) + +LIBSRCS = $(LIBOBJS:%.o=$(SRCDIR)/%.c) +lintcheck := SRCS = $(LIBSRCS) + +LIBS = $(DYNLIB) +LDLIBS += -lshare -lnsl -lscf -lumem -lc +all install := LDLIBS += -lxml2 + +CFLAGS += $(CCVERBOSE) +CPPFLAGS += -D_REENTRANT -I/usr/include/libxml2 \ + -I$(SRCDIR)/../common + +.KEEP_STATE: + +all: $(LIBS) + +install: all + +lint: lintcheck + +pics/smb_door_client.o: $(SMBBASE_DIR)/smb_door_client.c + $(COMPILE.c) -o $@ $(SMBBASE_DIR)/smb_door_client.c + $(POST_PROCESS_O) + +pics/smb_share_door_decode.o: $(SMBCOMMON_DIR)/smb_share_door_decode.c + $(COMPILE.c) -o $@ $(SMBCOMMON_DIR)/smb_share_door_decode.c + $(POST_PROCESS_O) + +pics/smb_common_door_decode.o: $(SMBCOMMON_DIR)/smb_common_door_decode.c + $(COMPILE.c) -o $@ $(SMBCOMMON_DIR)/smb_common_door_decode.c + $(POST_PROCESS_O) + +pics/smb_cfg.o: $(SMBBASE_DIR)/smb_cfg.c + $(COMPILE.c) -o $@ $(SMBBASE_DIR)/smb_cfg.c + $(POST_PROCESS_O) + +pics/smb_scfutil.o: $(SMBBASE_DIR)/smb_scfutil.c + $(COMPILE.c) -o $@ $(SMBBASE_DIR)/smb_scfutil.c + $(POST_PROCESS_O) + +pics/smb_share_util.o: $(SMBMLSVC_DIR)/smb_share_util.c + $(COMPILE.c) -o $@ $(SMBMLSVC_DIR)/smb_share_util.c + $(POST_PROCESS_O) + +include ../../../Makefile.targ diff --git a/usr/src/lib/libshare/smb/amd64/Makefile b/usr/src/lib/libshare/smb/amd64/Makefile new file mode 100644 index 0000000000..90b1730d23 --- /dev/null +++ b/usr/src/lib/libshare/smb/amd64/Makefile @@ -0,0 +1,31 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) diff --git a/usr/src/lib/libshare/smb/i386/Makefile b/usr/src/lib/libshare/smb/i386/Makefile new file mode 100644 index 0000000000..543238473c --- /dev/null +++ b/usr/src/lib/libshare/smb/i386/Makefile @@ -0,0 +1,30 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) diff --git a/usr/src/lib/libshare/smb/libshare_smb.c b/usr/src/lib/libshare/smb/libshare_smb.c new file mode 100644 index 0000000000..7d705e0369 --- /dev/null +++ b/usr/src/lib/libshare/smb/libshare_smb.c @@ -0,0 +1,1641 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * SMB specific functions + */ +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <zone.h> +#include <errno.h> +#include <locale.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <syslog.h> +#include "libshare.h" +#include "libshare_impl.h" +#include <pwd.h> +#include <limits.h> +#include <libscf.h> +#include <strings.h> +#include "libshare_smb.h" +#include <rpcsvc/daemon_utils.h> +#include <smbsrv/lmshare.h> +#include <smbsrv/lmshare_door.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/libsmb.h> + +/* internal functions */ +static int smb_share_init(void); +static void smb_share_fini(void); +static int smb_enable_share(sa_share_t); +static int smb_share_changed(sa_share_t); +static int smb_resource_changed(sa_resource_t); +static int smb_rename_resource(sa_handle_t, sa_resource_t, char *); +static int smb_disable_share(sa_share_t share, char *); +static int smb_validate_property(sa_property_t, sa_optionset_t); +static int smb_set_proto_prop(sa_property_t); +static sa_protocol_properties_t smb_get_proto_set(void); +static char *smb_get_status(void); +static int smb_parse_optstring(sa_group_t, char *); +static char *smb_format_options(sa_group_t, int); + +static int smb_enable_service(void); + +static int range_check_validator(int, char *); +static int range_check_validator_zero_ok(int, char *); +static int string_length_check_validator(int, char *); +static int true_false_validator(int, char *); +static int ip_address_validator_empty_ok(int, char *); +static int ip_address_csv_list_validator_empty_ok(int, char *); +static int ipc_mode_validator(int, char *); +static int path_validator(int, char *); + +static int smb_enable_resource(sa_resource_t); +static int smb_disable_resource(sa_resource_t); +static uint64_t smb_share_features(void); +static int smb_list_transient(sa_handle_t); + +/* size of basic format allocation */ +#define OPT_CHUNK 1024 + +/* + * Indexes of entries in smb_proto_options table. + * Changes to smb_proto_options table may require + * an update to these values. + */ +#define PROTO_OPT_WINS1 6 +#define PROTO_OPT_WINS_EXCLUDE 8 + + +/* + * ops vector that provides the protocol specific info and operations + * for share management. + */ + +struct sa_plugin_ops sa_plugin_ops = { + SA_PLUGIN_VERSION, + SMB_PROTOCOL_NAME, + smb_share_init, + smb_share_fini, + smb_enable_share, + smb_disable_share, + smb_validate_property, + NULL, + NULL, + smb_parse_optstring, + smb_format_options, + smb_set_proto_prop, + smb_get_proto_set, + smb_get_status, + NULL, + NULL, + NULL, + smb_share_changed, + smb_enable_resource, + smb_disable_resource, + smb_share_features, + smb_list_transient, + smb_resource_changed, + smb_rename_resource, + NULL, + NULL +}; + +/* + * option definitions. Make sure to keep the #define for the option + * index just before the entry it is the index for. Changing the order + * can cause breakage. + */ + +struct option_defs optdefs[] = { + {SHOPT_AD_CONTAINER, OPT_TYPE_STRING}, + {SHOPT_NAME, OPT_TYPE_NAME}, + {NULL, NULL}, +}; + +/* + * findopt(name) + * + * Lookup option "name" in the option table and return the table + * index. + */ + +static int +findopt(char *name) +{ + int i; + if (name != NULL) { + for (i = 0; optdefs[i].tag != NULL; i++) { + if (strcmp(optdefs[i].tag, name) == 0) + return (i); + } + } + return (-1); +} + +/* + * is_a_number(number) + * + * is the string a number in one of the forms we want to use? + */ + +static int +is_a_number(char *number) +{ + int ret = 1; + int hex = 0; + + if (strncmp(number, "0x", 2) == 0) { + number += 2; + hex = 1; + } else if (*number == '-') { + number++; /* skip the minus */ + } + + while (ret == 1 && *number != '\0') { + if (hex) { + ret = isxdigit(*number++); + } else { + ret = isdigit(*number++); + } + } + return (ret); +} + +/* + * validresource(name) + * + * Check that name only has valid characters in it. The current valid + * set are the printable characters but not including: + * " / \ [ ] : | < > + ; , ? * = \t + * Note that space is included and there is a maximum length. + */ +static int +validresource(const char *name) +{ + const char *cp; + size_t len; + + if (name == NULL) + return (B_FALSE); + + len = strlen(name); + if (len == 0 || len > SA_MAX_RESOURCE_NAME) + return (B_FALSE); + + if (strpbrk(name, "\"/\\[]:|<>+;,?*=\t") != NULL) { + return (B_FALSE); + } + + for (cp = name; *cp != '\0'; cp++) + if (iscntrl(*cp)) + return (B_FALSE); + + return (B_TRUE); +} + +/* + * smb_isonline() + * + * Determine if the SMF service instance is in the online state or + * not. A number of operations depend on this state. + */ +static boolean_t +smb_isonline(void) +{ + char *str; + boolean_t ret = B_FALSE; + + if ((str = smf_get_state(SMBD_DEFAULT_INSTANCE_FMRI)) != NULL) { + ret = (strcmp(str, SCF_STATE_STRING_ONLINE) == 0); + free(str); + } + return (ret); +} + +/* + * smb_enable_share tells the implementation that it is to enable the share. + * This entails converting the path and options into the appropriate ioctl + * calls. It is assumed that all error checking of paths, etc. were + * done earlier. + */ +static int +smb_enable_share(sa_share_t share) +{ + char *path; + char *rname; + lmshare_info_t si; + sa_resource_t resource; + boolean_t iszfs; + boolean_t privileged; + int err = SA_OK; + priv_set_t *priv_effective; + boolean_t online; + + priv_effective = priv_allocset(); + (void) getppriv(PRIV_EFFECTIVE, priv_effective); + privileged = (priv_isfullset(priv_effective) == B_TRUE); + priv_freeset(priv_effective); + + /* get the path since it is important in several places */ + path = sa_get_share_attr(share, "path"); + if (path == NULL) + return (SA_NO_SUCH_PATH); + + online = smb_isonline(); + + iszfs = sa_path_is_zfs(path); + + if (iszfs) { + + if (privileged == B_FALSE && !online) { + + if (!online) { + (void) printf(dgettext(TEXT_DOMAIN, + "SMB: Cannot share remove " + "file system: %s\n"), path); + (void) printf(dgettext(TEXT_DOMAIN, + "SMB: Service needs to be enabled " + "by a privileged user\n")); + err = SA_NO_PERMISSION; + errno = EPERM; + } + if (err) { + sa_free_attr_string(path); + return (err); + } + + } + } + + if (privileged == B_TRUE && !online) { + err = smb_enable_service(); + if (err != SA_OK) { + (void) printf(dgettext(TEXT_DOMAIN, + "SMB: Unable to enable service\n")); + /* + * For now, it is OK to not be able to enable + * the service. + */ + if (err == SA_BUSY) + err = SA_OK; + } else { + online = B_TRUE; + } + } + + /* + * Don't bother trying to start shares if the service isn't + * running. + */ + if (!online) + goto done; + + /* Each share can have multiple resources */ + for (resource = sa_get_share_resource(share, NULL); + resource != NULL; + resource = sa_get_next_resource(resource)) { + sa_optionset_t opts; + bzero(&si, sizeof (lmshare_info_t)); + rname = sa_get_resource_attr(resource, "name"); + if (rname == NULL) { + sa_free_attr_string(path); + return (SA_NO_SUCH_RESOURCE); + } + + opts = sa_get_derived_optionset(resource, SMB_PROTOCOL_NAME, 1); + smb_build_lmshare_info(rname, path, opts, &si); + sa_free_attr_string(rname); + + sa_free_derived_optionset(opts); + if (!iszfs) { + err = lmshrd_add(&si); + } else { + share_t sh; + + sa_sharetab_fill_zfs(share, &sh, "smb"); + err = sa_share_zfs(share, (char *)path, &sh, + &si, ZFS_SHARE_SMB); + + sa_emptyshare(&sh); + } + } + if (!iszfs) + (void) sa_update_sharetab(share, "smb"); +done: + sa_free_attr_string(path); + + return (err == NERR_DuplicateShare ? 0 : err); +} + +/* + * This is the share for CIFS all shares have resource names. + * Enable tells the smb server to update its hash. If it fails + * because smb server is down, we just ignore as smb server loads + * the resources from sharemanager at startup. + */ + +static int +smb_enable_resource(sa_resource_t resource) +{ + char *path; + char *rname; + sa_optionset_t opts; + sa_share_t share; + lmshare_info_t si; + int ret; + + share = sa_get_resource_parent(resource); + if (share == NULL) + return (SA_NO_SUCH_PATH); + path = sa_get_share_attr(share, "path"); + if (path == NULL) + return (SA_SYSTEM_ERR); + rname = sa_get_resource_attr(resource, "name"); + if (rname == NULL) { + sa_free_attr_string(path); + return (SA_NO_SUCH_RESOURCE); + } + + ret = smb_enable_service(); + + if (!smb_isonline()) { + ret = SA_OK; + goto done; + } + + opts = sa_get_derived_optionset(resource, SMB_PROTOCOL_NAME, 1); + smb_build_lmshare_info(rname, path, opts, &si); + sa_free_attr_string(path); + sa_free_attr_string(rname); + sa_free_derived_optionset(opts); + if (lmshrd_add(&si) != NERR_Success) + return (SA_NOT_SHARED); + (void) sa_update_sharetab(share, "smb"); + +done: + return (ret); +} + +/* + * Remove it from smb server hash. + */ +static int +smb_disable_resource(sa_resource_t resource) +{ + char *rname; + DWORD res; + sa_share_t share; + + rname = sa_get_resource_attr(resource, "name"); + if (rname == NULL) + return (SA_NO_SUCH_RESOURCE); + + if (smb_isonline()) { + res = lmshrd_delete(rname); + if (res != NERR_Success) { + sa_free_attr_string(rname); + return (SA_CONFIG_ERR); + } + sa_free_attr_string(rname); + rname = NULL; + } + share = sa_get_resource_parent(resource); + if (share != NULL) { + rname = sa_get_share_attr(share, "path"); + if (rname != NULL) { + (void) sa_delete_sharetab(rname, "smb"); + sa_free_attr_string(rname); + rname = NULL; + } + } + if (rname != NULL) + sa_free_attr_string(rname); + /* + * Always return OK as smb/server may be down and + * Shares will be picked up when loaded. + */ + return (SA_OK); +} + +/* + * smb_share_changed(sa_share_t share) + * + * The specified share has changed. + */ +static int +smb_share_changed(sa_share_t share) +{ + char *path; + sa_resource_t resource; + + /* get the path since it is important in several places */ + path = sa_get_share_attr(share, "path"); + if (path == NULL) + return (SA_NO_SUCH_PATH); + for (resource = sa_get_share_resource(share, NULL); + resource != NULL; + resource = sa_get_next_resource(resource)) + (void) smb_resource_changed(resource); + + sa_free_attr_string(path); + + return (SA_OK); +} + +/* + * smb_resource_changed(sa_resource_t resource) + * + * The specified resource has changed. + */ +static int +smb_resource_changed(sa_resource_t resource) +{ + DWORD res; + lmshare_info_t si; + lmshare_info_t new_si; + char *rname, *path; + sa_optionset_t opts; + sa_share_t share; + + rname = sa_get_resource_attr(resource, "name"); + if (rname == NULL) + return (SA_NO_SUCH_RESOURCE); + + share = sa_get_resource_parent(resource); + if (share == NULL) { + sa_free_attr_string(rname); + return (SA_CONFIG_ERR); + } + + path = sa_get_share_attr(share, "path"); + if (path == NULL) { + sa_free_attr_string(rname); + return (SA_NO_SUCH_PATH); + } + + if (!smb_isonline()) { + sa_free_attr_string(rname); + return (SA_OK); + } + + /* Update the share cache in smb/server */ + res = lmshrd_getinfo(rname, &si); + if (res != NERR_Success) { + sa_free_attr_string(path); + sa_free_attr_string(rname); + return (SA_CONFIG_ERR); + } + + opts = sa_get_derived_optionset(resource, SMB_PROTOCOL_NAME, 1); + smb_build_lmshare_info(rname, path, opts, &new_si); + sa_free_derived_optionset(opts); + sa_free_attr_string(path); + sa_free_attr_string(rname); + + /* + * Update all fields from sa_share_t + * Get derived values. + */ + if (lmshrd_setinfo(&new_si) != LMSHR_DOOR_SRV_SUCCESS) + return (SA_CONFIG_ERR); + return (smb_enable_service()); +} + +/* + * smb_disable_share(sa_share_t share) + * + * Unshare the specified share. + */ +static int +smb_disable_share(sa_share_t share, char *path) +{ + char *rname; + sa_resource_t resource; + boolean_t iszfs; + int err = SA_OK; + + iszfs = sa_path_is_zfs(path); + if (!smb_isonline()) + goto done; + + for (resource = sa_get_share_resource(share, NULL); + resource != NULL; + resource = sa_get_next_resource(resource)) { + rname = sa_get_resource_attr(resource, "name"); + if (rname == NULL) { + continue; + } + if (!iszfs) { + err = lmshrd_delete(rname); + switch (err) { + case NERR_NetNameNotFound: + case NERR_Success: + err = SA_OK; + break; + default: + err = SA_CONFIG_ERR; + break; + } + } else { + share_t sh; + + sa_sharetab_fill_zfs(share, &sh, "smb"); + err = sa_share_zfs(share, (char *)path, &sh, + rname, ZFS_UNSHARE_SMB); + sa_emptyshare(&sh); + } + sa_free_attr_string(rname); + } +done: + if (!iszfs) + (void) sa_delete_sharetab(path, "smb"); + return (err); +} + +/* + * smb_validate_property(property, parent) + * + * Check that the property has a legitimate value for its type. + */ + +static int +smb_validate_property(sa_property_t property, sa_optionset_t parent) +{ + int ret = SA_OK; + char *propname; + int optindex; + sa_group_t parent_group; + char *value; + + propname = sa_get_property_attr(property, "type"); + + if ((optindex = findopt(propname)) < 0) + ret = SA_NO_SUCH_PROP; + + /* need to validate value range here as well */ + if (ret == SA_OK) { + parent_group = sa_get_parent_group((sa_share_t)parent); + if (optdefs[optindex].share && !sa_is_share(parent_group)) + ret = SA_PROP_SHARE_ONLY; + } + if (ret != SA_OK) { + if (propname != NULL) + sa_free_attr_string(propname); + return (ret); + } + + value = sa_get_property_attr(property, "value"); + if (value != NULL) { + /* first basic type checking */ + switch (optdefs[optindex].type) { + case OPT_TYPE_NUMBER: + /* check that the value is all digits */ + if (!is_a_number(value)) + ret = SA_BAD_VALUE; + break; + case OPT_TYPE_BOOLEAN: + if (strlen(value) == 0 || + strcasecmp(value, "true") == 0 || + strcmp(value, "1") == 0 || + strcasecmp(value, "false") == 0 || + strcmp(value, "0") == 0) { + ret = SA_OK; + } else { + ret = SA_BAD_VALUE; + } + break; + case OPT_TYPE_NAME: + /* + * Make sure no invalid characters + */ + if (validresource(value) == B_FALSE) + ret = SA_BAD_VALUE; + break; + case OPT_TYPE_STRING: + /* whatever is here should be ok */ + break; + default: + break; + } + } + + if (value != NULL) + sa_free_attr_string(value); + if (ret == SA_OK && optdefs[optindex].check != NULL) + /* do the property specific check */ + ret = optdefs[optindex].check(property); + + if (propname != NULL) + sa_free_attr_string(propname); + return (ret); +} + +/* + * Protocol management functions + * + * properties defined in the default files are defined in + * proto_option_defs for parsing and validation. + */ + +struct smb_proto_option_defs { + char *name; /* display name -- remove protocol identifier */ + int smb_index; + int32_t minval; + int32_t maxval; /* In case of length of string this should be max */ + int (*validator)(int, char *); + int32_t refresh; +} smb_proto_options[] = { + { SMB_CD_SYS_CMNT, + SMB_CI_SYS_CMNT, 0, MAX_VALUE_BUFLEN, + string_length_check_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_MAX_WORKERS, + SMB_CI_MAX_WORKERS, 64, 1024, range_check_validator, + SMB_REFRESH_REFRESH}, + { SMB_CD_NBSCOPE, + SMB_CI_NBSCOPE, 0, MAX_VALUE_BUFLEN, + string_length_check_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_RDR_IPCMODE, + SMB_CI_RDR_IPCMODE, 0, 0, ipc_mode_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_LM_LEVEL, + SMB_CI_LM_LEVEL, 2, 5, range_check_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_KEEPALIVE, + SMB_CI_KEEPALIVE, 20, 5400, range_check_validator_zero_ok, + SMB_REFRESH_REFRESH}, + { SMB_CD_WINS_SRV1, + SMB_CI_WINS_SRV1, 0, MAX_VALUE_BUFLEN, + ip_address_validator_empty_ok, SMB_REFRESH_REFRESH}, + { SMB_CD_WINS_SRV2, + SMB_CI_WINS_SRV2, 0, MAX_VALUE_BUFLEN, + ip_address_validator_empty_ok, SMB_REFRESH_REFRESH}, + { SMB_CD_WINS_EXCL, + SMB_CI_WINS_EXCL, 0, MAX_VALUE_BUFLEN, + ip_address_csv_list_validator_empty_ok, SMB_REFRESH_REFRESH}, + { SMB_CD_SIGNING_ENABLE, + SMB_CI_SIGNING_ENABLE, 0, 0, true_false_validator, + SMB_REFRESH_REFRESH}, + { SMB_CD_SIGNING_REQD, + SMB_CI_SIGNING_REQD, 0, 0, true_false_validator, + SMB_REFRESH_REFRESH}, + { SMB_CD_RESTRICT_ANON, + SMB_CI_RESTRICT_ANON, 0, 0, true_false_validator, + SMB_REFRESH_REFRESH}, + { SMB_CD_DOMAIN_SRV, + SMB_CI_DOMAIN_SRV, 0, MAX_VALUE_BUFLEN, + ip_address_validator_empty_ok, SMB_REFRESH_REFRESH}, + { SMB_CD_ADS_ENABLE, + SMB_CI_ADS_ENABLE, 0, 0, true_false_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_ADS_USER, + SMB_CI_ADS_USER, 0, MAX_VALUE_BUFLEN, + string_length_check_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_ADS_USER_CONTAINER, + SMB_CI_ADS_USER_CONTAINER, 0, MAX_VALUE_BUFLEN, + string_length_check_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_ADS_DOMAIN, + SMB_CI_ADS_DOMAIN, 0, MAX_VALUE_BUFLEN, + string_length_check_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_ADS_PASSWD, + SMB_CI_ADS_PASSWD, 0, MAX_VALUE_BUFLEN, + string_length_check_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_ADS_IPLOOKUP, + SMB_CI_ADS_IPLOOKUP, 0, 0, true_false_validator, + SMB_REFRESH_REFRESH}, + { SMB_CD_ADS_SITE, + SMB_CI_ADS_SITE, 0, MAX_VALUE_BUFLEN, + string_length_check_validator, SMB_REFRESH_REFRESH}, + { SMB_CD_DYNDNS_ENABLE, + SMB_CI_DYNDNS_ENABLE, 0, 0, true_false_validator, + SMB_REFRESH_REFRESH}, + { SMB_CD_DYNDNS_RETRY_SEC, + SMB_CI_DYNDNS_RETRY_SEC, 0, 20, range_check_validator, + SMB_REFRESH_REFRESH}, + { SMB_CD_DYNDNS_RETRY_COUNT, + SMB_CI_DYNDNS_RETRY_COUNT, 3, 5, range_check_validator, + SMB_REFRESH_REFRESH}, + { SMB_CD_AUTOHOME_MAP, + SMB_CI_AUTOHOME_MAP, 0, MAX_VALUE_BUFLEN, + path_validator}, + {NULL, -1, 0, 0, NULL} +}; + +/* + * Check the range of value as int range. + */ +static int +range_check_validator(int index, char *value) +{ + int ret = SA_OK; + + if (!is_a_number(value)) { + ret = SA_BAD_VALUE; + } else { + int val; + val = strtoul(value, NULL, 0); + if (val < smb_proto_options[index].minval || + val > smb_proto_options[index].maxval) + ret = SA_BAD_VALUE; + } + return (ret); +} + +/* + * Check the range of value as int range. + */ +static int +range_check_validator_zero_ok(int index, char *value) +{ + int ret = SA_OK; + + if (!is_a_number(value)) { + ret = SA_BAD_VALUE; + } else { + int val; + val = strtoul(value, NULL, 0); + if (val == 0) + ret = SA_OK; + else { + if (val < smb_proto_options[index].minval || + val > smb_proto_options[index].maxval) + ret = SA_BAD_VALUE; + } + } + return (ret); +} + +/* + * Check the length of the string + */ +static int +string_length_check_validator(int index, char *value) +{ + int ret = SA_OK; + + if (value == NULL) + return (SA_BAD_VALUE); + if (strlen(value) > smb_proto_options[index].maxval) + ret = SA_BAD_VALUE; + return (ret); +} + +/* + * Check yes/no + */ +/*ARGSUSED*/ +static int +true_false_validator(int index, char *value) +{ + if (value == NULL) + return (SA_BAD_VALUE); + if ((strcasecmp(value, "true") == 0) || + (strcasecmp(value, "false") == 0)) + return (SA_OK); + return (SA_BAD_VALUE); +} + +/* + * Check IP address. + */ +/*ARGSUSED*/ +static int +ip_address_validator_empty_ok(int index, char *value) +{ + char sbytes[16]; + int len; + + if (value == NULL) + return (SA_OK); + len = strlen(value); + if (len == 0) + return (SA_OK); + if (inet_pton(AF_INET, value, (void *)sbytes) != 1) + return (SA_BAD_VALUE); + + return (SA_OK); +} + +/* + * Check IP address list + */ +/*ARGSUSED*/ +static int +ip_address_csv_list_validator_empty_ok(int index, char *value) +{ + char sbytes[16]; + char *ip, *tmp, *ctx; + + if (value == NULL || *value == '\0') + return (SA_OK); + + if (strlen(value) > MAX_VALUE_BUFLEN) + return (SA_BAD_VALUE); + + if ((tmp = strdup(value)) == NULL) + return (SA_NO_MEMORY); + + ip = strtok_r(tmp, ",", &ctx); + while (ip) { + if (strlen(ip) == 0) { + free(tmp); + return (SA_BAD_VALUE); + } + if (*ip != 0) { + if (inet_pton(AF_INET, ip, + (void *)sbytes) != 1) { + free(tmp); + return (SA_BAD_VALUE); + } + } + ip = strtok_r(0, ",", &ctx); + } + + free(tmp); + return (SA_OK); +} + +/* + * Check IPC mode + */ +/*ARGSUSED*/ +static int +ipc_mode_validator(int index, char *value) +{ + if (value == NULL) + return (SA_BAD_VALUE); + if (strcasecmp(value, "anon") == 0) + return (SA_OK); + if (strcasecmp(value, "auth") == 0) + return (SA_OK); + return (SA_BAD_VALUE); +} + +/* + * Check path + */ +/*ARGSUSED*/ +static int +path_validator(int index, char *value) +{ + struct stat buffer; + int fd, status; + + if (value == NULL) + return (SA_BAD_VALUE); + + fd = open(value, O_RDONLY); + if (fd < 0) + return (SA_BAD_VALUE); + + status = fstat(fd, &buffer); + (void) close(fd); + + if (status < 0) + return (SA_BAD_VALUE); + + if (buffer.st_mode & S_IFDIR) + return (SA_OK); + return (SA_BAD_VALUE); +} + +/* + * the protoset holds the defined options so we don't have to read + * them multiple times + */ +static sa_protocol_properties_t protoset; + +static int +findprotoopt(char *name) +{ + int i; + for (i = 0; smb_proto_options[i].name != NULL; i++) { + if (strcasecmp(smb_proto_options[i].name, name) == 0) + return (i); + } + return (-1); +} + +/* + * smb_load_proto_properties() + * + * read the smb config values from SMF. + */ + +static int +smb_load_proto_properties() +{ + sa_property_t prop; + int index; + char *value; + + protoset = sa_create_protocol_properties(SMB_PROTOCOL_NAME); + if (protoset == NULL) + return (SA_NO_MEMORY); + + if (smb_config_load() != 0) + return (SA_CONFIG_ERR); + for (index = 0; smb_proto_options[index].name != NULL; index++) { + value = smb_config_getenv(smb_proto_options[index].smb_index); + prop = sa_create_property( + smb_proto_options[index].name, value); + (void) sa_add_protocol_property(protoset, prop); + } + return (SA_OK); +} + +/* + * smb_share_init() + * + * Initialize the smb plugin. + */ + +static int +smb_share_init(void) +{ + int ret = SA_OK; + + if (sa_plugin_ops.sa_init != smb_share_init) + return (SA_SYSTEM_ERR); + + if (smb_load_proto_properties() != SA_OK) + return (SA_SYSTEM_ERR); + + return (ret); +} + +/* + * smb_share_fini() + * + */ +static void +smb_share_fini(void) +{ + xmlFreeNode(protoset); + protoset = NULL; +} + +/* + * smb_get_proto_set() + * + * Return an optionset with all the protocol specific properties in + * it. + */ +static sa_protocol_properties_t +smb_get_proto_set(void) +{ + return (protoset); +} + +/* + * How long to wait for service to come online + */ +#define WAIT_FOR_SERVICE 15 + +/* + * smb_enable_service() + * + */ +static int +smb_enable_service(void) +{ + int i; + int ret = SA_OK; + + if (!smb_isonline()) { + if (smf_enable_instance(SMBD_DEFAULT_INSTANCE_FMRI, 0) != 0) { + (void) fprintf(stderr, + dgettext(TEXT_DOMAIN, + "%s failed to restart: %s\n"), + scf_strerror(scf_error())); + return (SA_CONFIG_ERR); + } + + /* Wait for service to come online */ + for (i = 0; i < WAIT_FOR_SERVICE; i++) { + if (smb_isonline()) { + ret = SA_OK; + break; + } else { + ret = SA_BUSY; + (void) sleep(1); + } + } + } + return (ret); +} + +/* + * smb_validate_proto_prop(index, name, value) + * + * Verify that the property specified by name can take the new + * value. This is a sanity check to prevent bad values getting into + * the default files. + */ +static int +smb_validate_proto_prop(int index, char *name, char *value) +{ + if ((name == NULL) || (index < 0)) + return (SA_BAD_VALUE); + + if (smb_proto_options[index].validator == NULL) + return (SA_OK); + + if (smb_proto_options[index].validator(index, value) == SA_OK) + return (SA_OK); + return (SA_BAD_VALUE); +} + +/* + * smb_set_proto_prop(prop) + * + * check that prop is valid. + */ +/*ARGSUSED*/ +static int +smb_set_proto_prop(sa_property_t prop) +{ + int ret = SA_OK; + char *name; + char *value; + int index = -1; + + name = sa_get_property_attr(prop, "type"); + value = sa_get_property_attr(prop, "value"); + if (name != NULL && value != NULL) { + index = findprotoopt(name); + if (index >= 0) { + /* should test for valid value */ + ret = smb_validate_proto_prop(index, name, value); + if (ret == SA_OK) { + /* Save to SMF */ + smb_config_setenv( + smb_proto_options[index].smb_index, value); + /* + * Specialized refresh mechanisms can + * be flagged in the proto_options and + * processed here. + */ + if (smb_proto_options[index].refresh & + SMB_REFRESH_REFRESH) + (void) smf_refresh_instance( + SMBD_DEFAULT_INSTANCE_FMRI); + else if (smb_proto_options[index].refresh & + SMB_REFRESH_RESTART) + (void) smf_restart_instance( + SMBD_DEFAULT_INSTANCE_FMRI); + } + } + } + if (name != NULL) + sa_free_attr_string(name); + if (value != NULL) + sa_free_attr_string(value); + + return (ret); +} + +/* + * smb_get_status() + * + * What is the current status of the smbd? We use the SMF state here. + * Caller must free the returned value. + */ + +static char * +smb_get_status(void) +{ + char *state = NULL; + state = smf_get_state(SMBD_DEFAULT_INSTANCE_FMRI); + return (state != NULL ? state : "-"); +} + +/* + * This protocol plugin require resource names + */ +static uint64_t +smb_share_features(void) +{ + return (SA_FEATURE_RESOURCE | SA_FEATURE_ALLOWSUBDIRS | + SA_FEATURE_ALLOWPARDIRS); +} + +/* + * This should be used to convert lmshare_info to sa_resource_t + * Should only be needed to build temp shares/resources to be + * supplied to sharemanager to display temp shares. + */ +static int +smb_build_tmp_sa_resource(sa_handle_t handle, lmshare_info_t *si) +{ + int err; + sa_share_t share; + sa_group_t group; + sa_resource_t resource; + + if (si == NULL) + return (SA_INVALID_NAME); + + /* + * First determine if the "share path" is already shared + * somewhere. If it is, we have to use it as the authority on + * where the transient share lives so will use it's parent + * group. If it doesn't exist, it needs to land in "smb". + */ + + share = sa_find_share(handle, si->directory); + if (share != NULL) { + group = sa_get_parent_group(share); + } else { + group = smb_get_smb_share_group(handle); + if (group == NULL) + return (SA_NO_SUCH_GROUP); + share = sa_get_share(group, si->directory); + if (share == NULL) { + share = sa_add_share(group, si->directory, + SA_SHARE_TRANSIENT, &err); + if (share == NULL) + return (SA_NO_SUCH_PATH); + } + } + + /* + * Now handle the resource. Make sure that the resource is + * transient and added to the share. + */ + resource = sa_get_share_resource(share, si->share_name); + if (resource == NULL) { + resource = sa_add_resource(share, + si->share_name, SA_SHARE_TRANSIENT, &err); + if (resource == NULL) + return (SA_NO_SUCH_RESOURCE); + } + + /* set resource attributes now */ + (void) sa_set_resource_attr(resource, "description", si->comment); + (void) sa_set_resource_attr(resource, SHOPT_AD_CONTAINER, + si->container); + + return (SA_OK); +} + +/* + * Return smb transient shares. Note that we really want to look at + * all current shares from SMB in order to determine this. Transient + * shares should be those that don't appear in either the SMF or ZFS + * configurations. Those that are in the repositories will be + * filtered out by smb_build_tmp_sa_resource. + */ +static int +smb_list_transient(sa_handle_t handle) +{ + int i, offset, num; + lmshare_list_t list; + int res; + + num = lmshrd_num_shares(); + if (num <= 0) + return (SA_OK); + offset = 0; + while (lmshrd_list(offset, &list) != NERR_InternalError) { + if (list.no == 0) + break; + for (i = 0; i < list.no; i++) { + res = smb_build_tmp_sa_resource(handle, + &(list.smbshr[i])); + if (res != SA_OK) + return (res); + } + offset += list.no; + } + + return (SA_OK); +} + +/* + * fix_resource_name(share, name, prefix) + * + * Construct a name where the ZFS dataset has the prefix replaced with "name". + */ +static char * +fix_resource_name(sa_share_t share, char *name, char *prefix) +{ + char *dataset = NULL; + char *newname = NULL; + size_t psize; + size_t nsize; + + dataset = sa_get_share_attr(share, "dataset"); + + if (dataset != NULL && strcmp(dataset, prefix) != 0) { + psize = strlen(prefix); + if (strncmp(dataset, prefix, psize) == 0) { + /* need string plus ',' and NULL */ + nsize = (strlen(dataset) - psize) + strlen(name) + 2; + newname = calloc(nsize, 1); + if (newname != NULL) { + (void) snprintf(newname, nsize, "%s%s", name, + dataset + psize); + sa_fix_resource_name(newname); + } + sa_free_attr_string(dataset); + return (newname); + } + } + if (dataset != NULL) + sa_free_attr_string(dataset); + return (strdup(name)); +} + +/* + * smb_parse_optstring(group, options) + * + * parse a compact option string into individual options. This allows + * ZFS sharesmb and sharemgr "share" command to work. group can be a + * group, a share or a resource. + */ +static int +smb_parse_optstring(sa_group_t group, char *options) +{ + char *dup; + char *base; + char *lasts; + char *token; + sa_optionset_t optionset; + sa_group_t parent = NULL; + sa_resource_t resource = NULL; + int iszfs = 0; + int persist = 0; + int need_optionset = 0; + int ret = SA_OK; + sa_property_t prop; + + /* + * In order to not attempt to change ZFS properties unless + * absolutely necessary, we never do it in the legacy parsing + * so we need to keep track of this. + */ + if (sa_is_share(group)) { + char *zfs; + + parent = sa_get_parent_group(group); + if (parent != NULL) { + zfs = sa_get_group_attr(parent, "zfs"); + if (zfs != NULL) { + sa_free_attr_string(zfs); + iszfs = 1; + } + } + } else { + iszfs = sa_group_is_zfs(group); + /* + * If a ZFS group, then we need to see if a resource + * name is being set. If so, bail with + * SA_PROP_SHARE_ONLY, so we come back in with a share + * instead of a group. + */ + if (strncmp(options, "name=", sizeof ("name=") - 1) == 0 || + strstr(options, ",name=") != NULL) { + return (SA_PROP_SHARE_ONLY); + } + } + + /* do we have an existing optionset? */ + optionset = sa_get_optionset(group, "smb"); + if (optionset == NULL) { + /* didn't find existing optionset so create one */ + optionset = sa_create_optionset(group, "smb"); + if (optionset == NULL) + return (SA_NO_MEMORY); + } else { + /* + * If an optionset already exists, we've come through + * twice so ignore the second time. + */ + return (ret); + } + + /* We need a copy of options for the next part. */ + dup = strdup(options); + if (dup == NULL) + return (SA_NO_MEMORY); + + /* + * SMB properties are straightforward and are strings, + * integers or booleans. Properties are separated by + * commas. It will be necessary to parse quotes due to some + * strings not having a restricted characters set. + * + * Note that names will create a resource. For now, if there + * is a set of properties "before" the first name="", those + * properties will be placed on the group. + */ + persist = sa_is_persistent(group); + base = dup; + token = dup; + lasts = NULL; + while (token != NULL && ret == SA_OK) { + ret = SA_OK; + token = strtok_r(base, ",", &lasts); + base = NULL; + if (token != NULL) { + char *value; + /* + * All SMB properties have values so there + * MUST be an '=' character. If it doesn't, + * it is a syntax error. + */ + value = strchr(token, '='); + if (value != NULL) { + *value++ = '\0'; + } else { + ret = SA_SYNTAX_ERR; + break; + } + /* + * We may need to handle a "name" property + * that is a ZFS imposed resource name. Each + * name would trigger getting a new "resource" + * to put properties on. For now, assume no + * "name" property for special handling. + */ + + if (strcmp(token, "name") == 0) { + char *prefix; + char *name = NULL; + /* + * We have a name, so now work on the + * resource level. We have a "share" + * in "group" due to the caller having + * added it. If we are called with a + * group, the check for group/share + * at the beginning of this function + * will bail out the parse if there is a + * "name" but no share. + */ + if (!iszfs) { + ret = SA_SYNTAX_ERR; + break; + } + /* + * Make sure the parent group has the + * "prefix" property since we will + * need to use this for constructing + * inherited name= values. + */ + prefix = sa_get_group_attr(parent, "prefix"); + if (prefix == NULL) { + prefix = sa_get_group_attr(parent, + "name"); + if (prefix != NULL) { + (void) sa_set_group_attr(parent, + "prefix", prefix); + } + } + name = fix_resource_name((sa_share_t)group, + value, prefix); + if (name != NULL) { + resource = sa_add_resource( + (sa_share_t)group, name, + SA_SHARE_TRANSIENT, &ret); + sa_free_attr_string(name); + } else { + ret = SA_NO_MEMORY; + } + if (prefix != NULL) + sa_free_attr_string(prefix); + + /* A resource level optionset is needed */ + + need_optionset = 1; + if (resource == NULL) { + ret = SA_NO_MEMORY; + break; + } + continue; + } + + if (need_optionset) { + optionset = sa_create_optionset(resource, + "smb"); + need_optionset = 0; + } + + prop = sa_create_property(token, value); + if (prop == NULL) + ret = SA_NO_MEMORY; + else + ret = sa_add_property(optionset, prop); + if (ret != SA_OK) + break; + if (!iszfs) + ret = sa_commit_properties(optionset, !persist); + } + } + free(dup); + return (ret); +} + +/* + * smb_sprint_option(rbuff, rbuffsize, incr, prop, sep) + * + * provides a mechanism to format SMB properties into legacy output + * format. If the buffer would overflow, it is reallocated and grown + * as appropriate. Special cases of converting internal form of values + * to those used by "share" are done. this function does one property + * at a time. + */ + +static void +smb_sprint_option(char **rbuff, size_t *rbuffsize, size_t incr, + sa_property_t prop, int sep) +{ + char *name; + char *value; + int curlen; + char *buff = *rbuff; + size_t buffsize = *rbuffsize; + + name = sa_get_property_attr(prop, "type"); + value = sa_get_property_attr(prop, "value"); + if (buff != NULL) + curlen = strlen(buff); + else + curlen = 0; + if (name != NULL) { + int len; + len = strlen(name) + sep; + + /* + * A future RFE would be to replace this with more + * generic code and to possibly handle more types. + * + * For now, everything else is treated as a string. If + * we get any properties that aren't exactly + * name/value pairs, we may need to + * interpret/transform. + */ + if (value != NULL) + len += 1 + strlen(value); + + while (buffsize <= (curlen + len)) { + /* need more room */ + buffsize += incr; + buff = realloc(buff, buffsize); + *rbuff = buff; + *rbuffsize = buffsize; + if (buff == NULL) { + /* realloc failed so free everything */ + if (*rbuff != NULL) + free(*rbuff); + goto err; + } + } + if (buff == NULL) + goto err; + (void) snprintf(buff + curlen, buffsize - curlen, + "%s%s=%s", sep ? "," : "", + name, value != NULL ? value : "\"\""); + + } +err: + if (name != NULL) + sa_free_attr_string(name); + if (value != NULL) + sa_free_attr_string(value); +} + +/* + * smb_format_resource_options(resource, hier) + * + * format all the options on the group into a flattened option + * string. If hier is non-zero, walk up the tree to get inherited + * options. + */ + +static char * +smb_format_options(sa_group_t group, int hier) +{ + sa_optionset_t options = NULL; + sa_property_t prop; + int sep = 0; + char *buff; + size_t buffsize; + + + buff = malloc(OPT_CHUNK); + if (buff == NULL) + return (NULL); + + buff[0] = '\0'; + buffsize = OPT_CHUNK; + + /* + * We may have a an optionset relative to this item. format + * these if we find them and then add any security definitions. + */ + + options = sa_get_derived_optionset(group, "smb", hier); + + /* + * do the default set first but skip any option that is also + * in the protocol specific optionset. + */ + if (options != NULL) { + for (prop = sa_get_property(options, NULL); + prop != NULL; prop = sa_get_next_property(prop)) { + /* + * use this one since we skipped any + * of these that were also in + * optdefault + */ + smb_sprint_option(&buff, &buffsize, OPT_CHUNK, + prop, sep); + if (buff == NULL) { + /* + * buff could become NULL if there + * isn't enough memory for + * smb_sprint_option to realloc() + * as necessary. We can't really + * do anything about it at this + * point so we return NULL. The + * caller should handle the + * failure. + */ + if (options != NULL) + sa_free_derived_optionset( + options); + return (buff); + } + sep = 1; + } + } + + if (options != NULL) + sa_free_derived_optionset(options); + return (buff); +} + +/* + * smb_rename_resource(resource, newname) + * + * Change the current exported name of the resource to newname. + */ +/*ARGSUSED*/ +int +smb_rename_resource(sa_handle_t handle, sa_resource_t resource, char *newname) +{ + int ret = SA_OK; + int err; + char *oldname; + + oldname = sa_get_resource_attr(resource, "name"); + if (oldname == NULL) + return (SA_NO_SUCH_RESOURCE); + + err = lmshrd_rename(oldname, newname); + + /* improve error values somewhat */ + switch (err) { + case NERR_Success: + break; + case NERR_InternalError: + ret = SA_SYSTEM_ERR; + break; + case NERR_DuplicateShare: + ret = SA_DUPLICATE_NAME; + break; + default: + ret = SA_CONFIG_ERR; + break; + } + + return (ret); +} diff --git a/usr/src/lib/libshare/smb/libshare_smb.h b/usr/src/lib/libshare/smb/libshare_smb.h new file mode 100644 index 0000000000..2d86c9ed32 --- /dev/null +++ b/usr/src/lib/libshare/smb/libshare_smb.h @@ -0,0 +1,75 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * basic API declarations for share management + */ + +#ifndef _LIBSHARE_SMB_H +#define _LIBSHARE_SMB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <smbsrv/lmshare.h> + +/* + * defined options types. These should be in a file rather than + * compiled in. Until there is a plugin mechanism to add new types, + * this is sufficient. + */ +#define OPT_TYPE_ANY 0 +#define OPT_TYPE_STRING 1 +#define OPT_TYPE_BOOLEAN 2 +#define OPT_TYPE_NUMBER 3 +#define OPT_TYPE_PATH 4 +#define OPT_TYPE_PROTOCOL 5 +#define OPT_TYPE_NAME 6 + +struct option_defs { + char *tag; + int type; + int share; /* share only option */ + int (*check)(char *); +}; + +/* + * Sharectl property refresh types. Bit mask to indicate which type(s) + * of refresh might be needed on the service(s). + */ + +#define SMB_REFRESH_RESTART 0x0001 /* restart smb/server */ +#define SMB_REFRESH_REFRESH 0x0002 /* refresh smb/server */ + + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSHARE_SMB_H */ diff --git a/usr/src/lib/libshare/smb/mapfile-vers b/usr/src/lib/libshare/smb/mapfile-vers new file mode 100644 index 0000000000..18c216bf6b --- /dev/null +++ b/usr/src/lib/libshare/smb/mapfile-vers @@ -0,0 +1,34 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +SUNWprivate_1.1 { + global: + sa_plugin_ops; + local: + *; +}; + diff --git a/usr/src/lib/libshare/smb/smb_share_doorclnt.c b/usr/src/lib/libshare/smb/smb_share_doorclnt.c new file mode 100644 index 0000000000..0ebf61b7a9 --- /dev/null +++ b/usr/src/lib/libshare/smb/smb_share_doorclnt.c @@ -0,0 +1,951 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * User-space door client for LanMan share management. + */ + +#include <syslog.h> +#include <door.h> +#include <fcntl.h> +#include <stdarg.h> +#include <errno.h> +#include <string.h> +#include <strings.h> + +#include <smbsrv/libsmb.h> + +#include <smbsrv/lmshare.h> +#include <smbsrv/lmerr.h> +#include <smbsrv/lmshare_door.h> +#include <smbsrv/cifs.h> + +int lmshrd_fildes = -1; + +char *lmshrd_desc[] = { + "", + "LmshrdOpenIter", + "LmshrdCloseIter", + "LmshrdIterate", + "LmshrdNumShares", + "LmshrdDelete", + "LmshrdRename", + "LmshrdGetinfo", + "LmshrdAdd", + "LmshrdSetinfo", + "LmshrdExists", + "LmshrdIsSpecial", + "LmshrdIsRestricted", + "LmshrdIsAdmin", + "LmshrdIsValid", + "LmshrdIsDir", + "LmshrdList", + "LmshrdListTrans", + "LmshrdNumTrans", + "N/A", + 0 +}; + +/* + * Returns 0 on success. Otherwise, -1. + */ +static int +lmshrd_door_open(int opcode) +{ + int rc = 0; + + if (lmshrd_fildes == -1 && + (lmshrd_fildes = open(LMSHR_DOOR_NAME, O_RDONLY)) < 0) { + syslog(LOG_DEBUG, "%s: open %s failed %s", lmshrd_desc[opcode], + LMSHR_DOOR_NAME, strerror(errno)); + rc = -1; + } + return (rc); +} + +/* + * Return 0 upon success. Otherwise, -1. + */ +static int +lmshrd_door_check_srv_status(int opcode, smb_dr_ctx_t *dec_ctx) +{ + int status = smb_dr_get_int32(dec_ctx); + int err; + int rc = -1; + + switch (status) { + case LMSHR_DOOR_SRV_SUCCESS: + rc = 0; + break; + + case LMSHR_DOOR_SRV_ERROR: + err = smb_dr_get_uint32(dec_ctx); + syslog(LOG_ERR, "%s: Encountered door server error %s", + lmshrd_desc[opcode], strerror(err)); + break; + + default: + syslog(LOG_ERR, "%s: Unknown door server status", + lmshrd_desc[opcode]); + } + + if (rc != 0) { + if ((err = smb_dr_decode_finish(dec_ctx)) != 0) + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(err)); + } + + return (rc); +} + +uint64_t +lmshrd_open_iterator(int mode) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + unsigned int status = 0; + uint64_t lmshr_iter = 0; + int opcode = LMSHR_DOOR_OPEN_ITERATOR; + + if (lmshrd_door_open(opcode) == -1) + return (lmshr_iter); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (lmshr_iter); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_int32(enc_ctx, mode); + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (lmshr_iter); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (lmshr_iter); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (lmshr_iter); + } + + lmshr_iter = smb_dr_get_lmshr_iterator(dec_ctx); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (lmshr_iter); + } + + (void) free(buf); + return (lmshr_iter); +} + + +DWORD +lmshrd_close_iterator(uint64_t iterator) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + unsigned int status = 0; + int opcode = LMSHR_DOOR_CLOSE_ITERATOR; + + if (lmshrd_door_open(opcode) == -1) + return (NERR_InternalError); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (NERR_InternalError); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_lmshr_iterator(enc_ctx, iterator); + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (NERR_InternalError); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (NERR_InternalError); + } + + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + (void) free(buf); + return (NERR_Success); +} + +DWORD +lmshrd_iterate(uint64_t iterator, lmshare_info_t *si) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + unsigned int status = 0; + int opcode = LMSHR_DOOR_ITERATE; + + if (lmshrd_door_open(opcode) == -1) + return (NERR_InternalError); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (NERR_InternalError); + + bzero(si, sizeof (lmshare_info_t)); + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_lmshr_iterator(enc_ctx, iterator); + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (NERR_InternalError); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (NERR_InternalError); + } + + smb_dr_get_lmshare(dec_ctx, si); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + (void) free(buf); + return (NERR_Success); +} + +DWORD +lmshrd_list(int offset, lmshare_list_t *list) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + DWORD rc; + int opcode = LMSHR_DOOR_LIST; + + if (lmshrd_door_open(opcode) == -1) + return (NERR_InternalError); + + buf = malloc(LMSHR_DOOR_SIZE); + + if (!buf) + return (NERR_InternalError); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_int32(enc_ctx, offset); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (NERR_InternalError); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (NERR_InternalError); + } + + rc = smb_dr_get_uint32(dec_ctx); + smb_dr_get_lmshr_list(dec_ctx, list); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + (void) free(buf); + + return (rc); +} + +int +lmshrd_num_shares(void) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + unsigned int status = 0; + DWORD num_shares; + int opcode = LMSHR_DOOR_NUM_SHARES; + + if (lmshrd_door_open(opcode) == -1) + return (-1); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (-1); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, LMSHR_DOOR_NUM_SHARES); + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (-1); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (-1); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (-1); + } + + num_shares = smb_dr_get_uint32(dec_ctx); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (-1); + } + + (void) free(buf); + return (num_shares); +} + +DWORD +lmshrd_delete(char *share_name) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + DWORD rc; + int opcode = LMSHR_DOOR_DELETE; + + if (lmshrd_door_open(opcode) == -1) + return (NERR_InternalError); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (NERR_InternalError); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, LMSHR_DOOR_DELETE); + smb_dr_put_string(enc_ctx, share_name); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (NERR_InternalError); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (NERR_InternalError); + } + + rc = smb_dr_get_uint32(dec_ctx); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + (void) free(buf); + return (rc); + +} + +DWORD +lmshrd_rename(char *from, char *to) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + DWORD rc; + int opcode = LMSHR_DOOR_RENAME; + + if (lmshrd_door_open(opcode) == -1) + return (NERR_InternalError); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (NERR_InternalError); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, LMSHR_DOOR_RENAME); + smb_dr_put_string(enc_ctx, from); + smb_dr_put_string(enc_ctx, to); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (NERR_InternalError); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (NERR_InternalError); + } + + rc = smb_dr_get_uint32(dec_ctx); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + (void) free(buf); + return (rc); +} + +DWORD +lmshrd_getinfo(char *share_name, lmshare_info_t *si) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + DWORD rc; + int opcode = LMSHR_DOOR_GETINFO; + + if (lmshrd_door_open(opcode) == -1) + return (NERR_InternalError); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (NERR_InternalError); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, LMSHR_DOOR_GETINFO); + smb_dr_put_string(enc_ctx, share_name); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (NERR_InternalError); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (NERR_InternalError); + } + + rc = smb_dr_get_uint32(dec_ctx); + smb_dr_get_lmshare(dec_ctx, si); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + (void) free(buf); + return (rc); +} + +DWORD +lmshrd_add(lmshare_info_t *si) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + DWORD rc; + int opcode = LMSHR_DOOR_ADD; + + if (lmshrd_door_open(opcode) == -1) + return (NERR_InternalError); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (NERR_InternalError); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_lmshare(enc_ctx, si); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (NERR_InternalError); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (NERR_InternalError); + } + + rc = smb_dr_get_uint32(dec_ctx); + smb_dr_get_lmshare(dec_ctx, si); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + (void) free(buf); + return (rc); +} + +DWORD +lmshrd_setinfo(lmshare_info_t *si) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + DWORD rc; + int opcode = LMSHR_DOOR_SETINFO; + + if (lmshrd_door_open(opcode) == -1) + return (NERR_InternalError); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (NERR_InternalError); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_lmshare(enc_ctx, si); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (NERR_InternalError); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (NERR_InternalError); + } + + rc = smb_dr_get_uint32(dec_ctx); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + (void) free(buf); + return (rc); +} + +static int +lmshrd_check(char *share_name, int opcode) +{ + door_arg_t arg; + char *buf; + unsigned int used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status, rc; + + if (lmshrd_door_open(opcode) == -1) + return (NERR_InternalError); + + buf = malloc(LMSHR_DOOR_SIZE); + if (!buf) + return (NERR_InternalError); + + enc_ctx = smb_dr_encode_start(buf, LMSHR_DOOR_SIZE); + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_string(enc_ctx, share_name); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = LMSHR_DOOR_SIZE; + + if (door_call(lmshrd_fildes, &arg) < 0) { + syslog(LOG_DEBUG, "%s: Door call failed %s", + lmshrd_desc[opcode], strerror(errno)); + (void) free(buf); + lmshrd_fildes = -1; + return (NERR_InternalError); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (lmshrd_door_check_srv_status(opcode, dec_ctx) != 0) { + (void) free(buf); + return (NERR_InternalError); + } + + rc = smb_dr_get_int32(dec_ctx); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + lmshrd_desc[opcode], strerror(status)); + (void) free(buf); + return (NERR_InternalError); + } + + (void) free(buf); + return (rc); +} + +int +lmshrd_exists(char *share_name) +{ + return (lmshrd_check(share_name, LMSHR_DOOR_EXISTS)); +} + +int +lmshrd_is_special(char *share_name) +{ + return (lmshrd_check(share_name, LMSHR_DOOR_IS_SPECIAL)); +} + +int +lmshrd_is_restricted(char *share_name) +{ + return (lmshrd_check(share_name, LMSHR_DOOR_IS_RESTRICTED)); +} + +int +lmshrd_is_admin(char *share_name) +{ + return (lmshrd_check(share_name, LMSHR_DOOR_IS_ADMIN)); +} + +int +lmshrd_is_valid(char *share_name) +{ + return (lmshrd_check(share_name, LMSHR_DOOR_IS_VALID)); +} + +int +lmshrd_is_dir(char *path) +{ + return (lmshrd_check(path, LMSHR_DOOR_IS_DIR)); +} + +static char * +lmshare_decode_type(unsigned int stype) +{ + switch (stype) { + case STYPE_DISKTREE: + return ("Disk"); + case STYPE_PRINTQ: + return ("Print Queue"); + case STYPE_DEVICE: + return ("Device"); + case STYPE_IPC: + return ("IPC"); + case STYPE_DFS: + return ("DFS"); + case STYPE_SPECIAL: + return ("Special"); + default: + return ("Unknown"); + }; +} + + +static void +lmshare_loginfo(FILE *fp, lmshare_info_t *si) +{ + if (!si) { + return; + } + + (void) fprintf(fp, "\n%s Information:\n", si->share_name); + (void) fprintf(fp, "\tFolder: %s\n", si->directory); + (void) fprintf(fp, "\tType: %s\n", lmshare_decode_type(si->stype)); + (void) fprintf(fp, "\tComment: %s\n", si->comment); + + (void) fprintf(fp, "\tStatus: %s\n", + ((si->mode & LMSHRM_TRANS) ? "Transient" : "Permanent")); + + (void) fprintf(fp, "\tContainer: %s\n", si->container); +} + +int +lmshrd_dump_hash(char *logfname) +{ + lmshare_info_t si; + uint64_t it; + FILE *fp; + + if ((logfname == 0) || (*logfname == 0)) + fp = stdout; + else { + fp = fopen(logfname, "w"); + if (fp == 0) { + syslog(LOG_WARNING, "LmshareDump [%s]:" + " cannot create logfile", logfname); + syslog(LOG_WARNING, "LmshareDump:" + " output will be written on screen"); + } + } + + it = lmshrd_open_iterator(LMSHRM_PERM); + if (it == NULL) { + syslog(LOG_ERR, "LmshareDump: resource shortage"); + if (fp && fp != stdout) { + (void) fclose(fp); + } + return (1); + } + + if (lmshrd_iterate(it, &si) != NERR_Success) { + syslog(LOG_ERR, "LmshareDump: Iterator iterate failed"); + if (fp && fp != stdout) { + (void) fclose(fp); + } + return (1); + } + while (*si.share_name != 0) { + lmshare_loginfo(fp, &si); + if (lmshrd_iterate(it, &si) != NERR_Success) { + syslog(LOG_ERR, "LmshareDump: Iterator iterate failed"); + if (fp && fp != stdout) { + (void) fclose(fp); + } + return (1); + } + } + + if (lmshrd_close_iterator(it) != NERR_Success) { + syslog(LOG_ERR, "LmshareDump: Iterator close failed"); + if (fp && fp != stdout) { + (void) fclose(fp); + } + return (1); + } + if (fp && fp != stdout) { + (void) fclose(fp); + } + return (0); +} diff --git a/usr/src/lib/libshare/smb/sparc/Makefile b/usr/src/lib/libshare/smb/sparc/Makefile new file mode 100644 index 0000000000..543238473c --- /dev/null +++ b/usr/src/lib/libshare/smb/sparc/Makefile @@ -0,0 +1,30 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) diff --git a/usr/src/lib/libshare/smb/sparcv9/Makefile b/usr/src/lib/libshare/smb/sparcv9/Makefile new file mode 100644 index 0000000000..90b1730d23 --- /dev/null +++ b/usr/src/lib/libshare/smb/sparcv9/Makefile @@ -0,0 +1,31 @@ +# +# 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 +# +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) diff --git a/usr/src/lib/libsldap/common/ns_writes.c b/usr/src/lib/libsldap/common/ns_writes.c index 6c39272eba..4ac8c16c13 100644 --- a/usr/src/lib/libsldap/common/ns_writes.c +++ b/usr/src/lib/libsldap/common/ns_writes.c @@ -86,7 +86,7 @@ replace_mapped_attr_in_dn( *new_dn = NULL; /* - * seperate dn into individual componets + * separate dn into individual componets * e.g. * "automountKey=user_01" , "automountMapName_test=auto_home", ... */ diff --git a/usr/src/lib/libzfs/common/libzfs.h b/usr/src/lib/libzfs/common/libzfs.h index 73bd80d201..265d22e66b 100644 --- a/usr/src/lib/libzfs/common/libzfs.h +++ b/usr/src/lib/libzfs/common/libzfs.h @@ -110,6 +110,8 @@ enum { EZFS_BADPERMSET, /* invalid permission set name */ EZFS_NODELEGATION, /* delegated administration is disabled */ EZFS_PERMRDONLY, /* pemissions are readonly */ + EZFS_UNSHARESMBFAILED, /* failed to unshare over smb */ + EZFS_SHARESMBFAILED, /* failed to share over smb */ EZFS_UNKNOWN }; @@ -320,7 +322,6 @@ extern const char *zfs_get_name(const zfs_handle_t *); /* * zfs dataset property management */ -extern int zfs_prop_valid_for_type(zfs_prop_t, int); extern const char *zfs_prop_default_string(zfs_prop_t); extern uint64_t zfs_prop_default_numeric(zfs_prop_t); extern const char *zfs_prop_column_name(zfs_prop_t); @@ -459,15 +460,22 @@ extern int zfs_unshare(zfs_handle_t *); * Protocol-specific share support functions. */ extern boolean_t zfs_is_shared_nfs(zfs_handle_t *, char **); +extern boolean_t zfs_is_shared_smb(zfs_handle_t *, char **); extern int zfs_share_nfs(zfs_handle_t *); +extern int zfs_share_smb(zfs_handle_t *); +extern int zfs_shareall(zfs_handle_t *); extern int zfs_unshare_nfs(zfs_handle_t *, const char *); +extern int zfs_unshare_smb(zfs_handle_t *, const char *); extern int zfs_unshareall_nfs(zfs_handle_t *); +extern int zfs_unshareall_smb(zfs_handle_t *); +extern int zfs_unshareall_bypath(zfs_handle_t *, const char *); +extern int zfs_unshareall(zfs_handle_t *); extern boolean_t zfs_is_shared_iscsi(zfs_handle_t *); extern int zfs_share_iscsi(zfs_handle_t *); extern int zfs_unshare_iscsi(zfs_handle_t *); extern int zfs_iscsi_perm_check(libzfs_handle_t *, char *, ucred_t *); extern int zfs_deleg_share_nfs(libzfs_handle_t *, char *, char *, - void *, void *, int, boolean_t); + void *, void *, int, zfs_share_op_t); /* * When dealing with nvlists, verify() is extremely useful diff --git a/usr/src/lib/libzfs/common/libzfs_changelist.c b/usr/src/lib/libzfs/common/libzfs_changelist.c index 9ceefdc3b1..2b53f7d983 100644 --- a/usr/src/lib/libzfs/common/libzfs_changelist.c +++ b/usr/src/lib/libzfs/common/libzfs_changelist.c @@ -71,6 +71,7 @@ typedef struct prop_changenode { struct prop_changelist { zfs_prop_t cl_prop; zfs_prop_t cl_realprop; + zfs_prop_t cl_shareprop; /* used with sharenfs/sharesmb */ uu_list_pool_t *cl_pool; uu_list_t *cl_list; boolean_t cl_waslegacy; @@ -185,6 +186,7 @@ changelist_postfix(prop_changelist_t *clp) cn = uu_list_prev(clp->cl_list, cn)) { boolean_t sharenfs; + boolean_t sharesmb; /* * If we are in the global zone, but this dataset is exported @@ -222,14 +224,18 @@ changelist_postfix(prop_changelist_t *clp) /* * Remount if previously mounted or mountpoint was legacy, - * or sharenfs property is set. + * or sharenfs or sharesmb property is set. */ sharenfs = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS, shareopts, sizeof (shareopts), NULL, NULL, 0, B_FALSE) == 0) && (strcmp(shareopts, "off") != 0)); - if ((cn->cn_mounted || clp->cl_waslegacy || sharenfs) && - !zfs_is_mounted(cn->cn_handle, NULL) && + sharesmb = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARESMB, + shareopts, sizeof (shareopts), NULL, NULL, 0, + B_FALSE) == 0) && (strcmp(shareopts, "off") != 0)); + + if ((cn->cn_mounted || clp->cl_waslegacy || sharenfs || + sharesmb) && !zfs_is_mounted(cn->cn_handle, NULL) && zfs_mount(cn->cn_handle, NULL, 0) != 0) ret = -1; @@ -237,11 +243,16 @@ changelist_postfix(prop_changelist_t *clp) * We always re-share even if the filesystem is currently * shared, so that we can adopt any new options. */ - if (cn->cn_shared || clp->cl_waslegacy || sharenfs) { + if (cn->cn_shared || clp->cl_waslegacy || + sharenfs || sharesmb) { if (sharenfs) ret = zfs_share_nfs(cn->cn_handle); else ret = zfs_unshare_nfs(cn->cn_handle, NULL); + if (sharesmb) + ret = zfs_share_smb(cn->cn_handle); + else + ret = zfs_unshare_smb(cn->cn_handle, NULL); } } @@ -302,21 +313,22 @@ changelist_rename(prop_changelist_t *clp, const char *src, const char *dst) } /* - * Given a gathered changelist for the 'sharenfs' property, unshare all the - * datasets in the list. + * Given a gathered changelist for the 'sharenfs' or 'sharesmb' property, + * unshare all the datasets in the list. */ int -changelist_unshare(prop_changelist_t *clp) +changelist_unshare(prop_changelist_t *clp, zfs_share_proto_t *proto) { prop_changenode_t *cn; int ret = 0; - if (clp->cl_prop != ZFS_PROP_SHARENFS) + if (clp->cl_prop != ZFS_PROP_SHARENFS && + clp->cl_prop != ZFS_PROP_SHARESMB) return (0); for (cn = uu_list_first(clp->cl_list); cn != NULL; cn = uu_list_next(clp->cl_list, cn)) { - if (zfs_unshare_nfs(cn->cn_handle, NULL) != 0) + if (zfs_unshare_proto(cn->cn_handle, NULL, proto) != 0) ret = -1; } @@ -386,6 +398,7 @@ change_one(zfs_handle_t *zhp, void *data) char where[64]; prop_changenode_t *cn; zprop_source_t sourcetype; + zprop_source_t share_sourcetype; /* * We only want to unmount/unshare those filesystems that may inherit @@ -405,9 +418,25 @@ change_one(zfs_handle_t *zhp, void *data) return (0); } + /* + * If we are "watching" sharenfs or sharesmb + * then check out the companion property which is tracked + * in cl_shareprop + */ + if (clp->cl_shareprop != ZPROP_INVAL && + zfs_prop_get(zhp, clp->cl_shareprop, property, + sizeof (property), &share_sourcetype, where, sizeof (where), + B_FALSE) != 0) { + zfs_close(zhp); + return (0); + } + if (clp->cl_alldependents || clp->cl_allchildren || sourcetype == ZPROP_SRC_DEFAULT || - sourcetype == ZPROP_SRC_INHERITED) { + sourcetype == ZPROP_SRC_INHERITED || + (clp->cl_shareprop != ZPROP_INVAL && + (share_sourcetype == ZPROP_SRC_DEFAULT || + share_sourcetype == ZPROP_SRC_INHERITED))) { if ((cn = zfs_alloc(zfs_get_handle(zhp), sizeof (prop_changenode_t))) == NULL) { zfs_close(zhp); @@ -507,7 +536,8 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) * order, regardless of their position in the hierarchy. */ if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED || - prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS) { + prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS || + prop == ZFS_PROP_SHARESMB) { compare = compare_mountpoints; clp->cl_sorted = B_TRUE; } @@ -561,9 +591,19 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) if (clp->cl_prop != ZFS_PROP_MOUNTPOINT && clp->cl_prop != ZFS_PROP_SHARENFS && + clp->cl_prop != ZFS_PROP_SHARESMB && clp->cl_prop != ZFS_PROP_SHAREISCSI) return (clp); + /* + * If watching SHARENFS or SHARESMB then + * also watch its companion property. + */ + if (clp->cl_prop == ZFS_PROP_SHARENFS) + clp->cl_shareprop = ZFS_PROP_SHARESMB; + else if (clp->cl_prop == ZFS_PROP_SHARESMB) + clp->cl_shareprop = ZFS_PROP_SHARENFS; + if (clp->cl_alldependents) { if (zfs_iter_dependents(zhp, B_TRUE, change_one, clp) != 0) { changelist_free(clp); diff --git a/usr/src/lib/libzfs/common/libzfs_dataset.c b/usr/src/lib/libzfs/common/libzfs_dataset.c index 04a37032ea..db912e7f20 100644 --- a/usr/src/lib/libzfs/common/libzfs_dataset.c +++ b/usr/src/lib/libzfs/common/libzfs_dataset.c @@ -51,6 +51,7 @@ #include <sys/spa.h> #include <sys/zio.h> #include <sys/zap.h> +#include <sys/zfs_i18n.h> #include <libzfs.h> #include "zfs_namecheck.h" @@ -376,7 +377,7 @@ top: if (ioctl(hdl->libzfs_fd, ZFS_IOC_ROLLBACK, &zc) == 0) goto top; /* - * If we can sucessfully destroy it, pretend that it + * If we can successfully destroy it, pretend that it * never existed. */ if (ioctl(hdl->libzfs_fd, ZFS_IOC_DESTROY, &zc) == 0) { @@ -481,6 +482,8 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, char *strval; zfs_prop_t prop; nvlist_t *ret; + int chosen_normal = -1; + int chosen_utf = -1; if (type == ZFS_TYPE_SNAPSHOT) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, @@ -545,7 +548,7 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, } if (zfs_prop_readonly(prop) && - (prop != ZFS_PROP_VOLBLOCKSIZE || zhp != NULL)) { + (!zfs_prop_setonce(prop) || zhp != NULL)) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' is readonly"), propname); @@ -636,19 +639,23 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, /*FALLTHRU*/ + case ZFS_PROP_SHARESMB: case ZFS_PROP_SHARENFS: /* - * For the mountpoint and sharenfs properties, check if - * it can be set in a global/non-global zone based on + * For the mountpoint and sharenfs or sharesmb + * properties, check if it can be set in a + * global/non-global zone based on * the zoned property value: * * global zone non-global zone * -------------------------------------------------- * zoned=on mountpoint (no) mountpoint (yes) * sharenfs (no) sharenfs (no) + * sharesmb (no) sharesmb (no) * * zoned=off mountpoint (yes) N/A * sharenfs (yes) + * sharesmb (yes) */ if (zoned) { if (getzoneid() == GLOBAL_ZONEID) { @@ -659,7 +666,8 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, (void) zfs_error(hdl, EZFS_ZONED, errbuf); goto error; - } else if (prop == ZFS_PROP_SHARENFS) { + } else if (prop == ZFS_PROP_SHARENFS || + prop == ZFS_PROP_SHARESMB) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' cannot be set in " "a non-global zone"), propname); @@ -684,17 +692,24 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, * property. Now we want to make sure that the * property value is valid if it is sharenfs. */ - if (prop == ZFS_PROP_SHARENFS && + if ((prop == ZFS_PROP_SHARENFS || + prop == ZFS_PROP_SHARESMB) && strcmp(strval, "on") != 0 && strcmp(strval, "off") != 0) { + zfs_share_proto_t proto; + + if (prop == ZFS_PROP_SHARESMB) + proto = PROTO_SMB; + else + proto = PROTO_NFS; /* - * Must be an NFS option string so - * init the libshare in order to - * enable the parser and then parse - * the options. We use the control API - * since we don't care about the - * current configuration and don't + * Must be an valid sharing protocol + * option string so init the libshare + * in order to enable the parser and + * then parse the options. We use the + * control API since we don't care about + * the current configuration and don't * want the overhead of loading it * until we actually do something. */ @@ -714,7 +729,7 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, goto error; } - if (zfs_parse_options(strval, "nfs") != SA_OK) { + if (zfs_parse_options(strval, proto) != SA_OK) { /* * There was an error in parsing so * deal with it by issuing an error @@ -734,6 +749,12 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, } break; + case ZFS_PROP_UTF8ONLY: + chosen_utf = (int)intval; + break; + case ZFS_PROP_NORMALIZE: + chosen_normal = (int)intval; + break; } /* @@ -786,6 +807,27 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, } /* + * If normalization was chosen, but no UTF8 choice was made, + * enforce rejection of non-UTF8 names. + * + * If normalization was chosen, but rejecting non-UTF8 names + * was explicitly not chosen, it is an error. + */ + if (chosen_normal > ZFS_NORMALIZE_NONE && chosen_utf < 0) { + if (nvlist_add_uint64(ret, + zfs_prop_to_name(ZFS_PROP_UTF8ONLY), 1) != 0) { + (void) no_memory(hdl); + goto error; + } + } else if (chosen_normal > ZFS_NORMALIZE_NONE && chosen_utf == 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "'%s' must be set 'on' if normalization chosen"), + zfs_prop_to_name(ZFS_PROP_UTF8ONLY)); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + /* * If this is an existing volume, and someone is setting the volsize, * make sure that it matches the reservation, or add it if necessary. */ @@ -941,7 +983,7 @@ zfs_perms_add_who_nvlist(nvlist_t *who_nvp, uint64_t whoid, void *whostr, * whostr may be null for everyone or create perms. * who_type: is the type of entry in whostr. Typically this will be * ZFS_DELEG_WHO_UNKNOWN. - * perms: comman separated list of permissions. May be null if user + * perms: common separated list of permissions. May be null if user * is requested to remove permissions by who. * inherit: Specifies the inheritance of the permissions. Will be either * ZFS_DELEG_PERM_LOCAL and/or ZFS_DELEG_PERM_DESCENDENT. @@ -1833,6 +1875,11 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src, mntopt_on = MNTOPT_XATTR; mntopt_off = MNTOPT_NOXATTR; break; + + case ZFS_PROP_NBMAND: + mntopt_on = MNTOPT_NBMAND; + mntopt_off = MNTOPT_NONBMAND; + break; } /* @@ -1871,6 +1918,7 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src, case ZFS_PROP_READONLY: case ZFS_PROP_SETUID: case ZFS_PROP_XATTR: + case ZFS_PROP_NBMAND: *val = getprop_uint64(zhp, prop, source); if (hasmntopt(&mnt, mntopt_on) && !*val) { @@ -4177,7 +4225,7 @@ zfs_iscsi_perm_check(libzfs_handle_t *hdl, char *dataset, ucred_t *cred) int zfs_deleg_share_nfs(libzfs_handle_t *hdl, char *dataset, char *path, - void *export, void *sharetab, int sharemax, boolean_t share_on) + void *export, void *sharetab, int sharemax, zfs_share_op_t operation) { zfs_cmd_t zc = { 0 }; int error; @@ -4186,7 +4234,7 @@ zfs_deleg_share_nfs(libzfs_handle_t *hdl, char *dataset, char *path, (void) strlcpy(zc.zc_value, path, sizeof (zc.zc_value)); zc.zc_share.z_sharedata = (uint64_t)(uintptr_t)sharetab; zc.zc_share.z_exportdata = (uint64_t)(uintptr_t)export; - zc.zc_share.z_sharetype = share_on; + zc.zc_share.z_sharetype = operation; zc.zc_share.z_sharemax = sharemax; error = ioctl(hdl->libzfs_fd, ZFS_IOC_SHARE, &zc); diff --git a/usr/src/lib/libzfs/common/libzfs_impl.h b/usr/src/lib/libzfs/common/libzfs_impl.h index 19a7590cee..cfc03791dd 100644 --- a/usr/src/lib/libzfs/common/libzfs_impl.h +++ b/usr/src/lib/libzfs/common/libzfs_impl.h @@ -90,6 +90,23 @@ struct zpool_handle { diskaddr_t zpool_start_block; }; +typedef enum { + PROTO_NFS = 0, + PROTO_SMB = 1, + PROTO_END = 2 +} zfs_share_proto_t; + +/* + * The following can be used as a bitmask and any new values + * added must preserve that capability. + */ +typedef enum { + SHARED_NOT_SHARED = 0x0, + SHARED_ISCSI = 0x1, + SHARED_NFS = 0x2, + SHARED_SMB = 0x4 +} zfs_share_type_t; + int zfs_error(libzfs_handle_t *, int, const char *); int zfs_error_fmt(libzfs_handle_t *, int, const char *, ...); void zfs_error_aux(libzfs_handle_t *, const char *, ...); @@ -127,7 +144,7 @@ void changelist_rename(prop_changelist_t *, const char *, const char *); void changelist_remove(zfs_handle_t *, prop_changelist_t *); void changelist_free(prop_changelist_t *); prop_changelist_t *changelist_gather(zfs_handle_t *, zfs_prop_t, int); -int changelist_unshare(prop_changelist_t *); +int changelist_unshare(prop_changelist_t *, zfs_share_proto_t *); int changelist_haszonedchild(prop_changelist_t *); void remove_mountpoint(zfs_handle_t *); @@ -148,8 +165,10 @@ void namespace_clear(libzfs_handle_t *); extern int zfs_init_libshare(libzfs_handle_t *, int); extern void zfs_uninit_libshare(libzfs_handle_t *); -extern int zfs_parse_options(char *, char *); +extern int zfs_parse_options(char *, zfs_share_proto_t); +extern int zfs_unshare_proto(zfs_handle_t *zhp, + const char *, zfs_share_proto_t *); #ifdef __cplusplus } #endif diff --git a/usr/src/lib/libzfs/common/libzfs_mount.c b/usr/src/lib/libzfs/common/libzfs_mount.c index d7dd227bc8..6810f7efdc 100644 --- a/usr/src/lib/libzfs/common/libzfs_mount.c +++ b/usr/src/lib/libzfs/common/libzfs_mount.c @@ -45,11 +45,17 @@ * zfs_unshare() * * zfs_is_shared_nfs() - * zfs_share_nfs() - * zfs_unshare_nfs() - * zfs_unshareall_nfs() + * zfs_is_shared_smb() * zfs_is_shared_iscsi() + * zfs_share_proto() + * zfs_shareall(); * zfs_share_iscsi() + * zfs_unshare_nfs() + * zfs_unshare_smb() + * zfs_unshareall_nfs() + * zfs_unshareall_smb() + * zfs_unshareall() + * zfs_unshareall_bypath() * zfs_unshare_iscsi() * * The following functions are available for pool consumers, and will @@ -82,11 +88,46 @@ #include <sys/systeminfo.h> #define MAXISALEN 257 /* based on sysinfo(2) man page */ +static int zfs_share_proto(zfs_handle_t *, zfs_share_proto_t *); +zfs_share_type_t zfs_is_shared_proto(zfs_handle_t *, char **, + zfs_share_proto_t); + static int (*iscsitgt_zfs_share)(const char *); static int (*iscsitgt_zfs_unshare)(const char *); static int (*iscsitgt_zfs_is_shared)(const char *); static int (*iscsitgt_svc_online)(); +/* + * The share protocols table must be in the same order as the zfs_share_prot_t + * enum in libzfs_impl.h + */ +typedef struct { + zfs_prop_t p_prop; + char *p_name; + int p_share_err; + int p_unshare_err; +} proto_table_t; + +proto_table_t proto_table[PROTO_END] = { + {ZFS_PROP_SHARENFS, "nfs", EZFS_SHARENFSFAILED, EZFS_UNSHARENFSFAILED}, + {ZFS_PROP_SHARESMB, "smb", EZFS_SHARESMBFAILED, EZFS_UNSHARESMBFAILED}, +}; + +zfs_share_proto_t nfs_only[] = { + PROTO_NFS, + PROTO_END +}; + +zfs_share_proto_t smb_only[] = { + PROTO_SMB, + PROTO_END +}; +zfs_share_proto_t share_all_proto[] = { + PROTO_NFS, + PROTO_SMB, + PROTO_END +}; + #pragma init(zfs_iscsi_init) static void zfs_iscsi_init(void) @@ -111,29 +152,54 @@ zfs_iscsi_init(void) } /* - * Search the sharetab for the given mountpoint, returning true if it is found. + * Search the sharetab for the given mountpoint and protocol, returning + * a zfs_share_type_t value. */ -static boolean_t -is_shared(libzfs_handle_t *hdl, const char *mountpoint) +static zfs_share_type_t +is_shared(libzfs_handle_t *hdl, const char *mountpoint, zfs_share_proto_t proto) { char buf[MAXPATHLEN], *tab; + char *ptr; if (hdl->libzfs_sharetab == NULL) - return (0); + return (SHARED_NOT_SHARED); (void) fseek(hdl->libzfs_sharetab, 0, SEEK_SET); while (fgets(buf, sizeof (buf), hdl->libzfs_sharetab) != NULL) { /* the mountpoint is the first entry on each line */ - if ((tab = strchr(buf, '\t')) != NULL) { + if ((tab = strchr(buf, '\t')) == NULL) + continue; + + *tab = '\0'; + if (strcmp(buf, mountpoint) == 0) { + /* + * the protocol field is the third field + * skip over second field + */ + ptr = ++tab; + if ((tab = strchr(ptr, '\t')) == NULL) + continue; + ptr = ++tab; + if ((tab = strchr(ptr, '\t')) == NULL) + continue; *tab = '\0'; - if (strcmp(buf, mountpoint) == 0) - return (B_TRUE); + if (strcmp(ptr, + proto_table[proto].p_name) == 0) { + switch (proto) { + case PROTO_NFS: + return (SHARED_NFS); + case PROTO_SMB: + return (SHARED_SMB); + default: + return (0); + } + } } } - return (B_FALSE); + return (SHARED_NOT_SHARED); } /* @@ -349,12 +415,12 @@ zfs_unmount(zfs_handle_t *zhp, const char *mountpoint, int flags) /* * Unshare and unmount the filesystem */ - if (zfs_unshare_nfs(zhp, mntpt) != 0) + if (zfs_unshare_proto(zhp, mntpt, share_all_proto) != 0) return (-1); if (unmount_one(zhp->zfs_hdl, mntpt, flags) != 0) { free(mntpt); - (void) zfs_share_nfs(zhp); + (void) zfs_shareall(zhp); return (-1); } free(mntpt); @@ -387,10 +453,17 @@ zfs_unmountall(zfs_handle_t *zhp, int flags) boolean_t zfs_is_shared(zfs_handle_t *zhp) { + zfs_share_type_t rc = 0; + zfs_share_proto_t *curr_proto; + if (ZFS_IS_VOLUME(zhp)) return (zfs_is_shared_iscsi(zhp)); - return (zfs_is_shared_nfs(zhp, NULL)); + for (curr_proto = share_all_proto; *curr_proto != PROTO_END; + curr_proto++) + rc |= zfs_is_shared_proto(zhp, NULL, *curr_proto); + + return (rc ? B_TRUE : B_FALSE); } int @@ -399,7 +472,7 @@ zfs_share(zfs_handle_t *zhp) if (ZFS_IS_VOLUME(zhp)) return (zfs_share_iscsi(zhp)); - return (zfs_share_nfs(zhp)); + return (zfs_share_proto(zhp, share_all_proto)); } int @@ -408,32 +481,47 @@ zfs_unshare(zfs_handle_t *zhp) if (ZFS_IS_VOLUME(zhp)) return (zfs_unshare_iscsi(zhp)); - return (zfs_unshare_nfs(zhp, NULL)); + return (zfs_unshareall(zhp)); } /* * Check to see if the filesystem is currently shared. */ -boolean_t -zfs_is_shared_nfs(zfs_handle_t *zhp, char **where) +zfs_share_type_t +zfs_is_shared_proto(zfs_handle_t *zhp, char **where, zfs_share_proto_t proto) { char *mountpoint; + zfs_share_type_t rc; if (!zfs_is_mounted(zhp, &mountpoint)) - return (B_FALSE); + return (SHARED_NOT_SHARED); - if (is_shared(zhp->zfs_hdl, mountpoint)) { + if (rc = is_shared(zhp->zfs_hdl, mountpoint, proto)) { if (where != NULL) *where = mountpoint; else free(mountpoint); - return (B_TRUE); + return (rc); } else { free(mountpoint); - return (B_FALSE); + return (SHARED_NOT_SHARED); } } +boolean_t +zfs_is_shared_nfs(zfs_handle_t *zhp, char **where) +{ + return (zfs_is_shared_proto(zhp, where, + PROTO_NFS) != SHARED_NOT_SHARED); +} + +boolean_t +zfs_is_shared_smb(zfs_handle_t *zhp, char **where) +{ + return (zfs_is_shared_proto(zhp, where, + PROTO_SMB) != SHARED_NOT_SHARED); +} + /* * Make sure things will work if libshare isn't installed by using * wrapper functions that check to see that the pointers to functions @@ -552,12 +640,13 @@ zfs_uninit_libshare(libzfs_handle_t *zhandle) */ int -zfs_parse_options(char *options, char *proto) +zfs_parse_options(char *options, zfs_share_proto_t proto) { int ret; if (_sa_parse_legacy_options != NULL) - ret = _sa_parse_legacy_options(NULL, options, proto); + ret = _sa_parse_legacy_options(NULL, options, + proto_table[proto].p_name); else ret = SA_CONFIG_ERR; return (ret); @@ -609,74 +698,102 @@ zfs_sa_disable_share(sa_share_t share, char *proto) } /* - * Share the given filesystem according to the options in 'sharenfs'. We rely + * Share the given filesystem according to the options in the specified + * protocol specific properties (sharenfs, sharesmb). We rely * on "libshare" to the dirty work for us. */ -int -zfs_share_nfs(zfs_handle_t *zhp) +static int +zfs_share_proto(zfs_handle_t *zhp, zfs_share_proto_t *proto) { char mountpoint[ZFS_MAXPROPLEN]; char shareopts[ZFS_MAXPROPLEN]; libzfs_handle_t *hdl = zhp->zfs_hdl; sa_share_t share; + zfs_share_proto_t *curr_proto; int ret; if (!zfs_is_mountable(zhp, mountpoint, sizeof (mountpoint), NULL)) return (0); - /* - * Return success if there are no share options. - */ - if (zfs_prop_get(zhp, ZFS_PROP_SHARENFS, shareopts, sizeof (shareopts), - NULL, NULL, 0, B_FALSE) != 0 || - strcmp(shareopts, "off") == 0) - return (0); - - /* - * If the 'zoned' property is set, then zfs_is_mountable() will have - * already bailed out if we are in the global zone. But local - * zones cannot be NFS servers, so we ignore it for local zones as well. - */ - if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED)) - return (0); - if ((ret = zfs_init_libshare(hdl, SA_INIT_SHARE_API)) != SA_OK) { (void) zfs_error_fmt(hdl, EZFS_SHARENFSFAILED, dgettext(TEXT_DOMAIN, "cannot share '%s': %s"), zfs_get_name(zhp), _sa_errorstr(ret)); return (-1); } - share = zfs_sa_find_share(hdl->libzfs_sharehdl, mountpoint); - if (share != NULL) { - int err; - err = zfs_sa_enable_share(share, "nfs"); - if (err != SA_OK) { - (void) zfs_error_fmt(hdl, EZFS_SHARENFSFAILED, + + for (curr_proto = proto; *curr_proto != PROTO_END; curr_proto++) { + /* + * Return success if there are no share options. + */ + if (zfs_prop_get(zhp, proto_table[*curr_proto].p_prop, + shareopts, sizeof (shareopts), NULL, NULL, + 0, B_FALSE) != 0 || strcmp(shareopts, "off") == 0) + continue; + + /* + * If the 'zoned' property is set, then zfs_is_mountable() + * will have already bailed out if we are in the global zone. + * But local zones cannot be NFS servers, so we ignore it for + * local zones as well. + */ + if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED)) + continue; + + share = zfs_sa_find_share(hdl->libzfs_sharehdl, mountpoint); + if (share != NULL) { + int err; + err = zfs_sa_enable_share(share, + proto_table[*curr_proto].p_name); + if (err != SA_OK) { + (void) zfs_error_fmt(hdl, + proto_table[*curr_proto].p_share_err, + dgettext(TEXT_DOMAIN, "cannot share '%s'"), + zfs_get_name(zhp)); + return (-1); + } + } else { + (void) zfs_error_fmt(hdl, + proto_table[*curr_proto].p_share_err, dgettext(TEXT_DOMAIN, "cannot share '%s'"), zfs_get_name(zhp)); return (-1); } - } else { - (void) zfs_error_fmt(hdl, EZFS_SHARENFSFAILED, - dgettext(TEXT_DOMAIN, "cannot share '%s'"), - zfs_get_name(zhp)); - return (-1); - } + } return (0); } + +int +zfs_share_nfs(zfs_handle_t *zhp) +{ + return (zfs_share_proto(zhp, nfs_only)); +} + +int +zfs_share_smb(zfs_handle_t *zhp) +{ + return (zfs_share_proto(zhp, smb_only)); +} + +int +zfs_shareall(zfs_handle_t *zhp) +{ + return (zfs_share_proto(zhp, share_all_proto)); +} + /* * Unshare a filesystem by mountpoint. */ static int -unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint) +unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint, + zfs_share_proto_t proto) { sa_share_t share; int err; char *mntpt; - /* * Mountpoint could get trashed if libshare calls getmntany * which id does during API initialization, so strdup the @@ -696,7 +813,7 @@ unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint) free(mntpt); /* don't need the copy anymore */ if (share != NULL) { - err = zfs_sa_disable_share(share, "nfs"); + err = zfs_sa_disable_share(share, proto_table[proto].p_name); if (err != SA_OK) { return (zfs_error_fmt(hdl, EZFS_UNSHARENFSFAILED, dgettext(TEXT_DOMAIN, "cannot unshare '%s': %s"), @@ -714,7 +831,8 @@ unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint) * Unshare the given filesystem. */ int -zfs_unshare_nfs(zfs_handle_t *zhp, const char *mountpoint) +zfs_unshare_proto(zfs_handle_t *zhp, const char *mountpoint, + zfs_share_proto_t *proto) { struct mnttab search = { 0 }, entry; char *mntpt = NULL; @@ -724,19 +842,25 @@ zfs_unshare_nfs(zfs_handle_t *zhp, const char *mountpoint) search.mnt_fstype = MNTTYPE_ZFS; rewind(zhp->zfs_hdl->libzfs_mnttab); if (mountpoint != NULL) - mountpoint = mntpt = zfs_strdup(zhp->zfs_hdl, mountpoint); + mntpt = zfs_strdup(zhp->zfs_hdl, mountpoint); if (mountpoint != NULL || ((zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) && getmntany(zhp->zfs_hdl->libzfs_mnttab, &entry, &search) == 0)) { + zfs_share_proto_t *curr_proto; if (mountpoint == NULL) - mountpoint = entry.mnt_mountp; + mntpt = zfs_strdup(zhp->zfs_hdl, entry.mnt_mountp); - if (is_shared(zhp->zfs_hdl, mountpoint) && - unshare_one(zhp->zfs_hdl, zhp->zfs_name, mountpoint) != 0) { - if (mntpt != NULL) - free(mntpt); - return (-1); + for (curr_proto = proto; *curr_proto != PROTO_END; + curr_proto++) { + + if (is_shared(zhp->zfs_hdl, mntpt, *curr_proto) && + unshare_one(zhp->zfs_hdl, zhp->zfs_name, + mntpt, *curr_proto) != 0) { + if (mntpt != NULL) + free(mntpt); + return (-1); + } } } if (mntpt != NULL) @@ -745,11 +869,23 @@ zfs_unshare_nfs(zfs_handle_t *zhp, const char *mountpoint) return (0); } +int +zfs_unshare_nfs(zfs_handle_t *zhp, const char *mountpoint) +{ + return (zfs_unshare_proto(zhp, mountpoint, nfs_only)); +} + +int +zfs_unshare_smb(zfs_handle_t *zhp, const char *mountpoint) +{ + return (zfs_unshare_proto(zhp, mountpoint, smb_only)); +} + /* - * Same as zfs_unmountall(), but for NFS unshares. + * Same as zfs_unmountall(), but for NFS and SMB unshares. */ int -zfs_unshareall_nfs(zfs_handle_t *zhp) +zfs_unshareall_proto(zfs_handle_t *zhp, zfs_share_proto_t *proto) { prop_changelist_t *clp; int ret; @@ -758,12 +894,36 @@ zfs_unshareall_nfs(zfs_handle_t *zhp) if (clp == NULL) return (-1); - ret = changelist_unshare(clp); + ret = changelist_unshare(clp, proto); changelist_free(clp); return (ret); } +int +zfs_unshareall_nfs(zfs_handle_t *zhp) +{ + return (zfs_unshareall_proto(zhp, nfs_only)); +} + +int +zfs_unshareall_smb(zfs_handle_t *zhp) +{ + return (zfs_unshareall_proto(zhp, smb_only)); +} + +int +zfs_unshareall(zfs_handle_t *zhp) +{ + return (zfs_unshareall_proto(zhp, share_all_proto)); +} + +int +zfs_unshareall_bypath(zfs_handle_t *zhp, const char *mountpoint) +{ + return (zfs_unshare_proto(zhp, mountpoint, share_all_proto)); +} + /* * Remove the mountpoint associated with the current dataset, if necessary. * We only remove the underlying directory if: @@ -805,7 +965,7 @@ zfs_is_shared_iscsi(zfs_handle_t *zhp) * If iscsi deamon isn't running then we aren't shared */ if (iscsitgt_svc_online && iscsitgt_svc_online() == 1) - return (0); + return (B_FALSE); else return (iscsitgt_zfs_is_shared != NULL && iscsitgt_zfs_is_shared(zhp->zfs_name) != 0); @@ -853,7 +1013,7 @@ zfs_unshare_iscsi(zfs_handle_t *zhp) /* * Return if the volume is not shared */ - if (!zfs_is_shared_iscsi(zhp)) + if (zfs_is_shared_iscsi(zhp) != SHARED_ISCSI) return (0); /* @@ -1143,9 +1303,14 @@ zpool_disable_datasets(zpool_handle_t *zhp, boolean_t force) * Walk through and first unshare everything. */ for (i = 0; i < used; i++) { - if (is_shared(hdl, mountpoints[i]) && - unshare_one(hdl, mountpoints[i], mountpoints[i]) != 0) - goto out; + zfs_share_proto_t *curr_proto; + for (curr_proto = share_all_proto; *curr_proto != PROTO_END; + curr_proto++) { + if (is_shared(hdl, mountpoints[i], *curr_proto) && + unshare_one(hdl, mountpoints[i], + mountpoints[i], *curr_proto) != 0) + goto out; + } } /* diff --git a/usr/src/lib/libzfs/common/libzfs_util.c b/usr/src/lib/libzfs/common/libzfs_util.c index d446b32091..8a1ad6d656 100644 --- a/usr/src/lib/libzfs/common/libzfs_util.c +++ b/usr/src/lib/libzfs/common/libzfs_util.c @@ -136,6 +136,10 @@ libzfs_error_description(libzfs_handle_t *hdl) return (dgettext(TEXT_DOMAIN, "unshare(1M) failed")); case EZFS_SHARENFSFAILED: return (dgettext(TEXT_DOMAIN, "share(1M) failed")); + case EZFS_UNSHARESMBFAILED: + return (dgettext(TEXT_DOMAIN, "smb remove share failed")); + case EZFS_SHARESMBFAILED: + return (dgettext(TEXT_DOMAIN, "smb add share failed")); case EZFS_ISCSISVCUNAVAIL: return (dgettext(TEXT_DOMAIN, "iscsitgt service need to be enabled by " diff --git a/usr/src/lib/libzfs/common/mapfile-vers b/usr/src/lib/libzfs/common/mapfile-vers index 1ca0da3453..d7d68f8708 100644 --- a/usr/src/lib/libzfs/common/mapfile-vers +++ b/usr/src/lib/libzfs/common/mapfile-vers @@ -53,6 +53,7 @@ SUNWprivate_1.1 { zfs_is_shared; zfs_is_shared_iscsi; zfs_is_shared_nfs; + zfs_is_shared_smb; zfs_iter_children; zfs_iter_dependents; zfs_iter_filesystems; @@ -92,7 +93,9 @@ SUNWprivate_1.1 { zfs_rollback; zfs_send; zfs_share; + zfs_shareall; zfs_share_nfs; + zfs_share_smb; zfs_share_iscsi; zfs_snapshot; zfs_type_to_name; @@ -101,7 +104,11 @@ SUNWprivate_1.1 { zfs_unshare; zfs_unshare_iscsi; zfs_unshare_nfs; + zfs_unshare_smb; + zfs_unshareall; + zfs_unshareall_bypath; zfs_unshareall_nfs; + zfs_unshareall_smb; zpool_add; zpool_clear; zpool_close; diff --git a/usr/src/lib/libzpool/common/kernel.c b/usr/src/lib/libzpool/common/kernel.c index 12219b72a9..e8ce11b2a5 100644 --- a/usr/src/lib/libzpool/common/kernel.c +++ b/usr/src/lib/libzpool/common/kernel.c @@ -389,9 +389,10 @@ vn_open(char *path, int x1, int flags, int mode, vnode_t **vpp, int x2, int x3) return (0); } +/*ARGSUSED*/ int vn_openat(char *path, int x1, int flags, int mode, vnode_t **vpp, int x2, - int x3, vnode_t *startvp) + int x3, vnode_t *startvp, int fd) { char *realpath = umem_alloc(strlen(path) + 2, UMEM_NOFAIL); int ret; @@ -399,6 +400,7 @@ vn_openat(char *path, int x1, int flags, int mode, vnode_t **vpp, int x2, ASSERT(startvp == rootdir); (void) sprintf(realpath, "/%s", path); + /* fd ignored for now, need if want to simulate nbmand support */ ret = vn_open(realpath, x1, flags, mode, vpp, x2, x3); umem_free(realpath, strlen(path) + 2); @@ -622,7 +624,8 @@ kobj_open_file(char *name) vnode_t *vp; /* set vp as the _fd field of the file */ - if (vn_openat(name, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0, rootdir) != 0) + if (vn_openat(name, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0, rootdir, + -1) != 0) return ((void *)-1UL); file = umem_zalloc(sizeof (struct _buf), UMEM_NOFAIL); @@ -814,6 +817,14 @@ z_compress_level(void *dst, size_t *dstlen, const void *src, size_t srclen, return (ret); } +/*ARGSUSED*/ +size_t u8_textprep_str(char *i, size_t *il, char *o, size_t *ol, int nf, + size_t vers, int *err) +{ + *err = EINVAL; + return ((size_t)-1); +} + uid_t crgetuid(cred_t *cr) { diff --git a/usr/src/lib/libzpool/common/sys/zfs_context.h b/usr/src/lib/libzpool/common/sys/zfs_context.h index 628a9c25a4..7825bfcb08 100644 --- a/usr/src/lib/libzpool/common/sys/zfs_context.h +++ b/usr/src/lib/libzpool/common/sys/zfs_context.h @@ -322,6 +322,9 @@ extern void taskq_destroy(taskq_t *); extern void taskq_wait(taskq_t *); extern int taskq_member(taskq_t *, void *); +#define XVA_MAPSIZE 3 +#define XVA_MAGIC 0x78766174 + /* * vnodes */ @@ -331,41 +334,79 @@ typedef struct vnode { char *v_path; } vnode_t; + +typedef struct xoptattr { + timestruc_t xoa_createtime; /* Create time of file */ + uint8_t xoa_archive; + uint8_t xoa_system; + uint8_t xoa_readonly; + uint8_t xoa_hidden; + uint8_t xoa_nounlink; + uint8_t xoa_immutable; + uint8_t xoa_appendonly; + uint8_t xoa_nodump; + uint8_t xoa_settable; + uint8_t xoa_opaque; + uint8_t xoa_av_quarantined; + uint8_t xoa_av_modified; +} xoptattr_t; + typedef struct vattr { uint_t va_mask; /* bit-mask of attributes */ u_offset_t va_size; /* file size in bytes */ } vattr_t; -#define AT_TYPE 0x0001 -#define AT_MODE 0x0002 -#define AT_UID 0x0004 -#define AT_GID 0x0008 -#define AT_FSID 0x0010 -#define AT_NODEID 0x0020 -#define AT_NLINK 0x0040 -#define AT_SIZE 0x0080 -#define AT_ATIME 0x0100 -#define AT_MTIME 0x0200 -#define AT_CTIME 0x0400 -#define AT_RDEV 0x0800 -#define AT_BLKSIZE 0x1000 -#define AT_NBLOCKS 0x2000 -#define AT_SEQ 0x8000 + +typedef struct xvattr { + vattr_t xva_vattr; /* Embedded vattr structure */ + uint32_t xva_magic; /* Magic Number */ + uint32_t xva_mapsize; /* Size of attr bitmap (32-bit words) */ + uint32_t *xva_rtnattrmapp; /* Ptr to xva_rtnattrmap[] */ + uint32_t xva_reqattrmap[XVA_MAPSIZE]; /* Requested attrs */ + uint32_t xva_rtnattrmap[XVA_MAPSIZE]; /* Returned attrs */ + xoptattr_t xva_xoptattrs; /* Optional attributes */ +} xvattr_t; + +typedef struct vsecattr { + uint_t vsa_mask; /* See below */ + int vsa_aclcnt; /* ACL entry count */ + void *vsa_aclentp; /* pointer to ACL entries */ + int vsa_dfaclcnt; /* default ACL entry count */ + void *vsa_dfaclentp; /* pointer to default ACL entries */ + size_t vsa_aclentsz; /* ACE size in bytes of vsa_aclentp */ +} vsecattr_t; + +#define AT_TYPE 0x00001 +#define AT_MODE 0x00002 +#define AT_UID 0x00004 +#define AT_GID 0x00008 +#define AT_FSID 0x00010 +#define AT_NODEID 0x00020 +#define AT_NLINK 0x00040 +#define AT_SIZE 0x00080 +#define AT_ATIME 0x00100 +#define AT_MTIME 0x00200 +#define AT_CTIME 0x00400 +#define AT_RDEV 0x00800 +#define AT_BLKSIZE 0x01000 +#define AT_NBLOCKS 0x02000 +#define AT_SEQ 0x08000 +#define AT_XVATTR 0x10000 #define CRCREAT 0 -#define VOP_CLOSE(vp, f, c, o, cr) 0 -#define VOP_PUTPAGE(vp, of, sz, fl, cr) 0 -#define VOP_GETATTR(vp, vap, fl, cr) ((vap)->va_size = (vp)->v_size, 0) +#define VOP_CLOSE(vp, f, c, o, cr, ct) 0 +#define VOP_PUTPAGE(vp, of, sz, fl, cr, ct) 0 +#define VOP_GETATTR(vp, vap, fl, cr, ct) ((vap)->va_size = (vp)->v_size, 0) -#define VOP_FSYNC(vp, f, cr) fsync((vp)->v_fd) +#define VOP_FSYNC(vp, f, cr, ct) fsync((vp)->v_fd) #define VN_RELE(vp) vn_close(vp) extern int vn_open(char *path, int x1, int oflags, int mode, vnode_t **vpp, int x2, int x3); extern int vn_openat(char *path, int x1, int oflags, int mode, vnode_t **vpp, - int x2, int x3, vnode_t *vp); + int x2, int x3, vnode_t *vp, int fd); extern int vn_rdwr(int uio, vnode_t *vp, void *addr, ssize_t len, offset_t offset, int x1, int x2, rlim64_t x3, void *x4, ssize_t *residp); extern void vn_close(vnode_t *vp); @@ -453,6 +494,21 @@ struct bootstat { uint64_t st_size; }; +typedef struct ace_object { + uid_t a_who; + uint32_t a_access_mask; + uint16_t a_flags; + uint16_t a_type; + uint8_t a_obj_type[16]; + uint8_t a_inherit_obj_type[16]; +} ace_object_t; + + +#define ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE 0x05 +#define ACE_ACCESS_DENIED_OBJECT_ACE_TYPE 0x06 +#define ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE 0x07 +#define ACE_SYSTEM_ALARM_OBJECT_ACE_TYPE 0x08 + extern struct _buf *kobj_open_file(char *name); extern int kobj_read_file(struct _buf *file, char *buf, unsigned size, unsigned off); @@ -464,6 +520,25 @@ extern int zfs_secpolicy_rename_perms(const char *from, const char *to, extern int zfs_secpolicy_destroy_perms(const char *name, cred_t *cr); extern zoneid_t getzoneid(void); +/* + * UTF-8 text preparation functions and their macros. + * (sunddi.h) + */ +#define U8_STRCMP_CS 0x00000001 +#define U8_STRCMP_CI_UPPER 0x00000002 +#define U8_STRCMP_CI_LOWER 0x00000004 + +#define U8_TEXTPREP_TOUPPER U8_STRCMP_CI_UPPER +#define U8_TEXTPREP_TOLOWER U8_STRCMP_CI_LOWER +#define U8_TEXTPREP_IGNORE_NULL 0x00010000 + +#define U8_UNICODE_320 (0) +#define U8_UNICODE_500 (1) +#define U8_UNICODE_LATEST U8_UNICODE_500 + +extern size_t u8_textprep_str(char *, size_t *, char *, size_t *, int, size_t, + int *); + #ifdef __cplusplus } #endif diff --git a/usr/src/lib/pam_modules/Makefile b/usr/src/lib/pam_modules/Makefile index c5692283be..0dce8eb7b1 100644 --- a/usr/src/lib/pam_modules/Makefile +++ b/usr/src/lib/pam_modules/Makefile @@ -49,7 +49,8 @@ SUBDIRS = \ unix_auth \ unix_account \ unix_cred \ - unix_session + unix_session \ + smb $(CLOSED_BUILD)SUBDIRS += \ $(CLOSED)/lib/pam_modules/smartcard diff --git a/usr/src/lib/pam_modules/smb/Makefile b/usr/src/lib/pam_modules/smb/Makefile new file mode 100644 index 0000000000..ea50c3ae1f --- /dev/null +++ b/usr/src/lib/pam_modules/smb/Makefile @@ -0,0 +1,56 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../../Makefile.lib + +TEXT_DOMAIN= SUNW_OST_SYSOSPAM +POFILE= smb_passwd.po +MSGFILES= smb_passwd.c + +SUBDIRS= $(MACH) +#$(BUILD64)SUBDIRS += $(MACH64) + +all := TARGET= all +clean := TARGET= clean +clobber := TARGET= clobber +install := TARGET= install +lint := TARGET= lint + +.KEEP_STATE: + +all clean clobber install lint: $(SUBDIRS) + +_msg: $(MSGDOMAINPOFILE) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include $(SRC)/Makefile.msg.targ +include ../../Makefile.targ diff --git a/usr/src/lib/pam_modules/smb/Makefile.com b/usr/src/lib/pam_modules/smb/Makefile.com new file mode 100644 index 0000000000..8afe5b8232 --- /dev/null +++ b/usr/src/lib/pam_modules/smb/Makefile.com @@ -0,0 +1,42 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +LIBRARY= pam_smb_passwd.a +VERS= .1 +OBJECTS= smb_passwd.o + +include ../../Makefile.pam_modules + +LDLIBS += -lpam -lc +LDLIBS += -L$(ROOT)/usr/lib/smbsrv -lsmb + +all: $(LIBS) + +lint: lintcheck + +include $(SRC)/lib/Makefile.targ diff --git a/usr/src/lib/pam_modules/smb/amd64/Makefile b/usr/src/lib/pam_modules/smb/amd64/Makefile new file mode 100644 index 0000000000..8b0fed8b9f --- /dev/null +++ b/usr/src/lib/pam_modules/smb/amd64/Makefile @@ -0,0 +1,35 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +DYNFLAGS += -R/usr/lib/smbsrv/$(MACH64) +DYNFLAGS += $(ROOT)/usr/lib/$(MACH64)/passwdutil.so.1 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) diff --git a/usr/src/lib/pam_modules/smb/i386/Makefile b/usr/src/lib/pam_modules/smb/i386/Makefile new file mode 100644 index 0000000000..5be2ca90df --- /dev/null +++ b/usr/src/lib/pam_modules/smb/i386/Makefile @@ -0,0 +1,34 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +DYNFLAGS += -R/usr/lib/smbsrv +DYNFLAGS += $(ROOT)/usr/lib/passwdutil.so.1 + +install: all $(ROOTLIBS) $(ROOTLINKS) diff --git a/usr/src/lib/pam_modules/smb/mapfile-vers b/usr/src/lib/pam_modules/smb/mapfile-vers new file mode 100644 index 0000000000..2a7c980a33 --- /dev/null +++ b/usr/src/lib/pam_modules/smb/mapfile-vers @@ -0,0 +1,33 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +SUNW_1.1 { + global: + pam_sm_chauthtok; + local: + *; +}; diff --git a/usr/src/lib/pam_modules/smb/smb_passwd.c b/usr/src/lib/pam_modules/smb/smb_passwd.c new file mode 100644 index 0000000000..9167d093a6 --- /dev/null +++ b/usr/src/lib/pam_modules/smb/smb_passwd.c @@ -0,0 +1,202 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <sys/varargs.h> +#include <string.h> +#include <syslog.h> +#include <stdlib.h> + +#include <security/pam_appl.h> +#include <security/pam_modules.h> +#include <security/pam_impl.h> + +#include <libintl.h> +#include <passwdutil.h> + +#include <smbsrv/libsmb.h> + +/*PRINTFLIKE3*/ +static void +error(boolean_t nowarn, pam_handle_t *pamh, char *fmt, ...) +{ + va_list ap; + char message[PAM_MAX_MSG_SIZE]; + + if (nowarn) + return; + + va_start(ap, fmt); + (void) vsnprintf(message, sizeof (message), fmt, ap); + (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, &message, + NULL); + va_end(ap); +} + +/*PRINTFLIKE3*/ +static void +info(boolean_t nowarn, pam_handle_t *pamh, char *fmt, ...) +{ + va_list ap; + char message[PAM_MAX_MSG_SIZE]; + + if (nowarn) + return; + + va_start(ap, fmt); + (void) vsnprintf(message, sizeof (message), fmt, ap); + (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, &message, + NULL); + va_end(ap); +} + +int +pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + boolean_t debug = B_FALSE; + boolean_t nowarn = B_FALSE; + pwu_repository_t files_rep; + char *user, *local_user; + char *newpw; + char *service; + int privileged; + int res; + int i; + + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "debug") == 0) + debug = B_TRUE; + else if (strcmp(argv[i], "nowarn") == 0) + nowarn = B_TRUE; + } + + if ((flags & PAM_PRELIM_CHECK) != 0) + return (PAM_IGNORE); + + if ((flags & PAM_UPDATE_AUTHTOK) == 0) + return (PAM_SYSTEM_ERR); + + if ((flags & PAM_SILENT) != 0) + nowarn = B_TRUE; + + if (debug) + __pam_log(LOG_AUTH | LOG_DEBUG, + "pam_smb_passwd: storing authtok"); + + (void) pam_get_item(pamh, PAM_SERVICE, (void **)&service); + (void) pam_get_item(pamh, PAM_USER, (void **)&user); + + if (user == NULL || *user == '\0') { + __pam_log(LOG_AUTH | LOG_ERR, + "pam_smb_passwd: username is empty"); + return (PAM_USER_UNKNOWN); + } + + (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpw); + if (newpw == NULL) { + /* + * A module on the stack has removed PAM_AUTHTOK. We fail + */ + return (PAM_AUTHTOK_ERR); + } + + /* Check to see if this is a local user */ + files_rep.type = "files"; + files_rep.scope = NULL; + files_rep.scope_len = 0; + res = __user_to_authenticate(user, &files_rep, &local_user, + &privileged); + if (res != PWU_SUCCESS) { + switch (res) { + case PWU_NOT_FOUND: + /* if not a local user, ignore */ + if (debug) { + __pam_log(LOG_AUTH | LOG_DEBUG, + "pam_smb_passwd: %s is not local", user); + } + return (PAM_IGNORE); + case PWU_DENIED: + return (PAM_PERM_DENIED); + } + return (PAM_SYSTEM_ERR); + } + + res = smb_pwd_setpasswd(user, newpw); + + /* + * now map the various return states to user messages + * and PAM return codes. + */ + switch (res) { + case SMB_PWE_SUCCESS: + info(nowarn, pamh, dgettext(TEXT_DOMAIN, + "%s: SMB password successfully changed for %s"), + service, user); + return (PAM_SUCCESS); + + case SMB_PWE_STAT_FAILED: + __pam_log(LOG_AUTH | LOG_ERR, + "%s: stat of SMB password file failed", service); + return (PAM_SYSTEM_ERR); + + case SMB_PWE_OPEN_FAILED: + case SMB_PWE_WRITE_FAILED: + case SMB_PWE_CLOSE_FAILED: + case SMB_PWE_UPDATE_FAILED: + error(nowarn, pamh, dgettext(TEXT_DOMAIN, + "%s: Unexpected failure. SMB password database unchanged."), + service); + return (PAM_SYSTEM_ERR); + + case SMB_PWE_BUSY: + error(nowarn, pamh, dgettext(TEXT_DOMAIN, + "%s: SMB password database busy. Try again later."), + service); + + return (PAM_AUTHTOK_LOCK_BUSY); + + case SMB_PWE_USER_UNKNOWN: + error(nowarn, pamh, dgettext(TEXT_DOMAIN, + "%s: %s does not exist."), service, user); + return (PAM_USER_UNKNOWN); + + case SMB_PWE_USER_DISABLE: + error(nowarn, pamh, dgettext(TEXT_DOMAIN, + "%s: %s is disable. SMB password database unchanged."), + service, user); + return (PAM_IGNORE); + + case SMB_PWE_DENIED: + return (PAM_PERM_DENIED); + + default: + res = PAM_SYSTEM_ERR; + break; + } + + return (res); +} diff --git a/usr/src/lib/pam_modules/smb/sparc/Makefile b/usr/src/lib/pam_modules/smb/sparc/Makefile new file mode 100644 index 0000000000..5be2ca90df --- /dev/null +++ b/usr/src/lib/pam_modules/smb/sparc/Makefile @@ -0,0 +1,34 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +DYNFLAGS += -R/usr/lib/smbsrv +DYNFLAGS += $(ROOT)/usr/lib/passwdutil.so.1 + +install: all $(ROOTLIBS) $(ROOTLINKS) diff --git a/usr/src/lib/pam_modules/smb/sparcv9/Makefile b/usr/src/lib/pam_modules/smb/sparcv9/Makefile new file mode 100644 index 0000000000..8b0fed8b9f --- /dev/null +++ b/usr/src/lib/pam_modules/smb/sparcv9/Makefile @@ -0,0 +1,35 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +DYNFLAGS += -R/usr/lib/smbsrv/$(MACH64) +DYNFLAGS += $(ROOT)/usr/lib/$(MACH64)/passwdutil.so.1 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) diff --git a/usr/src/lib/smbsrv/Makefile b/usr/src/lib/smbsrv/Makefile new file mode 100644 index 0000000000..a57e621ffe --- /dev/null +++ b/usr/src/lib/smbsrv/Makefile @@ -0,0 +1,43 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.lib + +SUBDIRS = \ + libmlsvc \ + libmlrpc \ + libsmb \ + libsmbns \ + libsmbrdr + +libmlrpc: libsmb +libsmbrdr: libsmb +libsmbns: libsmb +libmlsvc: libsmb libmlrpc libsmbrdr libsmbns + +include ./Makefile.subdirs diff --git a/usr/src/lib/smbsrv/Makefile.lib b/usr/src/lib/smbsrv/Makefile.lib new file mode 100644 index 0000000000..abd5776bc9 --- /dev/null +++ b/usr/src/lib/smbsrv/Makefile.lib @@ -0,0 +1,50 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" + +# +# Common Makefile definitions for smbsrv. +# + +# We reset the Makefile.lib macros ROOTLIBDIR to refer to usr/lib/smbsrv +# We also install the userland library header files under /usr/include/smbsrv +ROOTSMBHDRDIR= $(ROOTHDRDIR)/smbsrv +ROOTSMBHDRS= $(HDRS:%=$(ROOTSMBHDRDIR)/%) + +ROOTLIBDIR = $(ROOT)/usr/lib/smbsrv + +SRCDIR= ../common +NDLDIR= $(ROOT)/usr/include/smbsrv/ndl +LIBS= $(DYNLIB) $(LINTLIB) +C99MODE = -xc99=%all +C99LMODE = -Xc99=%all +CPPFLAGS += -I$(SRCDIR) -I. +DYNFLAGS += -R/usr/lib/smbsrv +DYNFLAGS64 += -R/usr/lib/smbsrv/$(MACH64) +LDLIBS32 += -L$(ROOT)/usr/lib/smbsrv +LDLIBS64 += -L$(ROOT)/usr/lib/smbsrv/$(MACH64) +$(LINTLIB) := SRCS = $(SRCDIR)/$(LINTSRC) + +CLEANFILES += $(OBJECTS:%_ndr.o=%_ndr.c) diff --git a/usr/src/lib/smbsrv/Makefile.smbsrv b/usr/src/lib/smbsrv/Makefile.smbsrv new file mode 100644 index 0000000000..16d6ddfed1 --- /dev/null +++ b/usr/src/lib/smbsrv/Makefile.smbsrv @@ -0,0 +1,65 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" + +# +# Toplevel Makefile included by each subdirectory. Responsible for the 'check' +# and 'install_h' targets, as well as descending into the architecture directory +# to actually build the library. +# + +include ../../Makefile.lib +include ../Makefile.lib + +SUBDIRS= $(MACH) +#$(BUILD64)SUBDIRS += $(MACH64) + +HDRDIR= common + +all := TARGET = all +clean := TARGET = clean +clobber := TARGET = clobber +install := TARGET = install +lint := TARGET = lint + +.KEEP_STATE: + +all clean clobber lint: $(SUBDIRS) + +install: install_h $(SUBDIRS) + +check: $(CHECKHDRS) + +install_h: $(ROOTSMBHDRS) + +$(SUBDIRS): FRC + @cd $@; pwd; VERSION='$(VERSION)' $(MAKE) $(TARGET) + +FRC: + +$(ROOTSMBHDRDIR)/%: common/% + $(INS.file) + +include ../../Makefile.targ diff --git a/usr/src/lib/smbsrv/Makefile.subdirs b/usr/src/lib/smbsrv/Makefile.subdirs new file mode 100644 index 0000000000..928a4e2167 --- /dev/null +++ b/usr/src/lib/smbsrv/Makefile.subdirs @@ -0,0 +1,42 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" + +.KEEP_STATE: + +all := TARGET = all +check := TARGET = check +clean := TARGET = clean +clobber := TARGET = clobber +install := TARGET = install +install_h := TARGET = install_h +lint := TARGET = lint + +all check clean clobber install install_h lint: $(SUBDIRS) + +$(SUBDIRS): FRC + @cd $@; pwd; VERSION='$(VERSION)' $(MAKE) $(TARGET) + +FRC: diff --git a/usr/src/lib/smbsrv/Makefile.targ b/usr/src/lib/smbsrv/Makefile.targ new file mode 100644 index 0000000000..92a05ef243 --- /dev/null +++ b/usr/src/lib/smbsrv/Makefile.targ @@ -0,0 +1,42 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" + +# +# Common targets for smbsrv Makefiles +# + +%_ndr.c: $(NDLDIR)/%.ndl + $(NDRGEN) -Y $(CC) $< + +pics/%.o: $(SRC)/common/smbsrv/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) + +.KEEP_STATE: + +all: $(LIBS) + +lint: lintcheck diff --git a/usr/src/lib/smbsrv/libmlrpc/Makefile b/usr/src/lib/smbsrv/libmlrpc/Makefile new file mode 100644 index 0000000000..c5a61203cd --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +HDRS= libmlrpc.h + +include ../Makefile.smbsrv diff --git a/usr/src/lib/smbsrv/libmlrpc/Makefile.com b/usr/src/lib/smbsrv/libmlrpc/Makefile.com new file mode 100644 index 0000000000..86c7672546 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/Makefile.com @@ -0,0 +1,57 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +LIBRARY = libmlrpc.a +VERS = .1 + +OBJS_COMMON = \ + mlndo.o \ + mlndr.o \ + mlrpc_client.o \ + mlrpc_encdec.o \ + mlrpc_heap.o \ + mlrpc_server.o \ + mlrpc_svc.o + +NDLLIST = rpcpdu + +OBJECTS= $(OBJS_COMMON) $(OBJS_SHARED) $(NDLLIST:%=%_ndr.o) + +include ../../../Makefile.lib +include ../../Makefile.lib + +INCS += -I$(SRC)/common/smbsrv + +LDLIBS += -lsmb -lc + +CPPFLAGS += $(INCS) -D_REENTRANT + +SRCS= $(OBJS_COMMON:%.o=$(SRCDIR)/%.c) \ + $(OBJS_SHARED:%.o=$(SRC)/common/smbsrv/%.c) + +include ../../Makefile.targ +include ../../../Makefile.targ diff --git a/usr/src/lib/smbsrv/libmlrpc/amd64/Makefile b/usr/src/lib/smbsrv/libmlrpc/amd64/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/amd64/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/smbsrv/libmlrpc/common/libmlrpc.h b/usr/src/lib/smbsrv/libmlrpc/common/libmlrpc.h new file mode 100644 index 0000000000..9a20054d8f --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/libmlrpc.h @@ -0,0 +1,41 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBMLRPC_H +#define _LIBMLRPC_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + + + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBMLRPC_H */ diff --git a/usr/src/lib/smbsrv/libmlrpc/common/llib-lmlrpc b/usr/src/lib/smbsrv/libmlrpc/common/llib-lmlrpc new file mode 100644 index 0000000000..1621e3c2f6 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/llib-lmlrpc @@ -0,0 +1,31 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include <smbsrv/libmlrpc.h> diff --git a/usr/src/lib/smbsrv/libmlrpc/common/mapfile-vers b/usr/src/lib/smbsrv/libmlrpc/common/mapfile-vers new file mode 100644 index 0000000000..6858b4cea0 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/mapfile-vers @@ -0,0 +1,57 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +SUNWprivate { + global: + mlndr_inner; + mlndr_params; + mlndr_topmost; + mlnds_destruct; + mlnds_initialize; + mlrpc_binding_pool_initialize; + mlrpc_c_bind; + mlrpc_c_call; + mlrpc_c_free_heap; + mlrpc_heap_avail; + mlrpc_heap_create; + mlrpc_heap_destroy; + mlrpc_heap_malloc; + mlrpc_heap_mkvcs; + mlrpc_heap_strsave; + mlrpc_heap_used; + mlrpc_register_service; + mlsvc_lookup_context; + mlsvc_rpc_process; + mlsvc_rpc_release; + ndt__char; + ndt_s_wchar; + ndt__uchar; + ndt__ulong; + ndt__ushort; + local: + *; +}; diff --git a/usr/src/lib/smbsrv/libmlrpc/common/mlndo.c b/usr/src/lib/smbsrv/libmlrpc/common/mlndo.c new file mode 100644 index 0000000000..c469223c4d --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/mlndo.c @@ -0,0 +1,534 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * MLRPC server-side NDR stream (PDU) operations. Stream operations + * should return TRUE (non-zero) on success or FALSE (zero or a null + * pointer) on failure. When an operation returns FALSE, including + * mlndo_malloc() returning NULL, it should set the mlnds->error to + * indicate what went wrong. + * + * When available, the relevant ndr_reference is passed to the + * operation but keep in mind that it may be a null pointer. + * + * Functions mlndo_get_pdu(), mlndo_put_pdu(), and mlndo_pad_pdu() + * must never grow the PDU data. A request for out-of-bounds data is + * an error. The swap_bytes flag is 1 if NDR knows that the byte- + * order in the PDU is different from the local system. + */ + +#include <sys/types.h> +#include <stdarg.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <string.h> +#include <assert.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/mlrpc.h> +#include <smbsrv/ndr.h> +#include <smbsrv/ntstatus.h> + +#define NDOBUFSZ 128 + +#define NDR_PDU_BLOCK_SIZE (4*1024) +#define NDR_PDU_BLOCK_MASK (NDR_PDU_BLOCK_SIZE - 1) +#define NDR_PDU_ALIGN(N) \ + (((N) + NDR_PDU_BLOCK_SIZE) & ~NDR_PDU_BLOCK_MASK) +#define NDR_PDU_MAX_SIZE (64*1024*1024) + +static char *mlndo_malloc(struct mlndr_stream *, unsigned, + struct ndr_reference *); +static int mlndo_free(struct mlndr_stream *, char *, struct ndr_reference *); +static int mlndo_grow_pdu(struct mlndr_stream *, unsigned long, + struct ndr_reference *); +static int mlndo_pad_pdu(struct mlndr_stream *, unsigned long, unsigned long, + struct ndr_reference *); +static int mlndo_get_pdu(struct mlndr_stream *, unsigned long, unsigned long, + char *, int, struct ndr_reference *); +static int mlndo_put_pdu(struct mlndr_stream *, unsigned long, unsigned long, + char *, int, struct ndr_reference *); +static void mlndo_tattle(struct mlndr_stream *, char *, struct ndr_reference *); +static void mlndo_tattle_error(struct mlndr_stream *, struct ndr_reference *); +static int mlndo_reset(struct mlndr_stream *); +static void mlndo_destruct(struct mlndr_stream *); +static void mlndo_hexfmt(uint8_t *, int, int, char *, int); + +/* + * The mlndr stream operations table. + */ +static struct mlndr_stream_ops mlnds_ops = { + mlndo_malloc, + mlndo_free, + mlndo_grow_pdu, + mlndo_pad_pdu, + mlndo_get_pdu, + mlndo_put_pdu, + mlndo_tattle, + mlndo_tattle_error, + mlndo_reset, + mlndo_destruct +}; + +/* + * mlnds_bswap + * + * Copies len bytes from src to dst such that dst contains the bytes + * from src in reverse order. + * + * We expect to be dealing with bytes, words, dwords etc. So the + * length must be non-zero and a power of 2. + */ +void +mlnds_bswap(void *srcbuf, void *dstbuf, size_t len) +{ + uint8_t *src = (uint8_t *)srcbuf; + uint8_t *dst = (uint8_t *)dstbuf; + + if ((len != 0) && ((len & (len - 1)) == 0)) { + src += len; + + while (len--) + *dst++ = *(--src); + } +} + +/* + * mlnds_initialize + * + * Initialize a stream. Sets up the PDU parameters and assigns the stream + * operations and the reference to the heap. An external heap is provided + * to the stream, rather than each stream creating its own heap. + */ +int +mlnds_initialize(struct mlndr_stream *mlnds, unsigned pdu_size_hint, + int composite_op, mlrpc_heap_t *heap) +{ + unsigned size; + + assert(mlnds); + assert(heap); + + bzero(mlnds, sizeof (*mlnds)); + + if (pdu_size_hint > NDR_PDU_MAX_SIZE) + return (0); + + size = (pdu_size_hint == 0) ? NDR_PDU_BLOCK_SIZE : pdu_size_hint; + mlnds->pdu_base_addr = malloc(size); + assert(mlnds->pdu_base_addr); + + mlnds->pdu_max_size = size; + mlnds->pdu_size = 0; + mlnds->pdu_base_offset = (unsigned long)mlnds->pdu_base_addr; + + mlnds->mlndo = &mlnds_ops; + mlnds->heap = (struct mlrpc_heap *)heap; + + mlnds->m_op = composite_op & 0x0F; + mlnds->dir = composite_op & 0xF0; + + mlnds->outer_queue_tailp = &mlnds->outer_queue_head; + return (1); +} + +/* + * mlnds_destruct + * + * Destroy a stream. This is an external interface to provide access to + * the stream's destruct operation. + */ +void +mlnds_destruct(struct mlndr_stream *mlnds) +{ + MLNDS_DESTRUCT(mlnds); +} + +/* + * mlndo_malloc + * + * Allocate memory from the stream heap. + */ +/*ARGSUSED*/ +static char * +mlndo_malloc(struct mlndr_stream *mlnds, unsigned len, + struct ndr_reference *ref) +{ + return (mlrpc_heap_malloc((mlrpc_heap_t *)mlnds->heap, len)); +} + +/* + * mlndo_free + * + * Always succeeds: cannot free individual stream allocations. + */ +/*ARGSUSED*/ +static int +mlndo_free(struct mlndr_stream *mlnds, char *p, struct ndr_reference *ref) +{ + return (1); +} + +/* + * mlndo_grow_pdu + * + * This is the only place that should change the size of the PDU. If the + * desired offset is beyond the current PDU size, we realloc the PDU + * buffer to accommodate the request. For efficiency, the PDU is always + * extended to a NDR_PDU_BLOCK_SIZE boundary. Requests to grow the PDU + * beyond NDR_PDU_MAX_SIZE are rejected. + * + * Returns 1 to indicate success. Otherwise 0 to indicate failure. + */ +static int +mlndo_grow_pdu(struct mlndr_stream *mlnds, unsigned long want_end_offset, + struct ndr_reference *ref) +{ + unsigned char *pdu_addr; + unsigned pdu_max_size; + + mlndo_printf(mlnds, ref, "grow %d", want_end_offset); + + pdu_max_size = mlnds->pdu_max_size; + + if (want_end_offset > pdu_max_size) { + pdu_max_size = NDR_PDU_ALIGN(want_end_offset); + + if (pdu_max_size >= NDR_PDU_MAX_SIZE) + return (0); + + pdu_addr = realloc(mlnds->pdu_base_addr, pdu_max_size); + if (pdu_addr == 0) + return (0); + + mlnds->pdu_max_size = pdu_max_size; + mlnds->pdu_base_addr = pdu_addr; + mlnds->pdu_base_offset = (unsigned long)pdu_addr; + } + + mlnds->pdu_size = want_end_offset; + return (1); +} + +static int +mlndo_pad_pdu(struct mlndr_stream *mlnds, unsigned long pdu_offset, + unsigned long n_bytes, struct ndr_reference *ref) +{ + unsigned char *data; + + data = (unsigned char *)mlnds->pdu_base_offset; + data += pdu_offset; + + mlndo_printf(mlnds, ref, "pad %d@%-3d", n_bytes, pdu_offset); + + bzero(data, n_bytes); + return (1); +} + +/* + * mlndo_get_pdu + * + * The swap flag is 1 if NDR knows that the byte-order in the PDU + * is different from the local system. + * + * Returns 1 on success or 0 to indicate failure. + */ +static int +mlndo_get_pdu(struct mlndr_stream *mlnds, unsigned long pdu_offset, + unsigned long n_bytes, char *buf, int swap_bytes, + struct ndr_reference *ref) +{ + unsigned char *data; + char hexbuf[NDOBUFSZ]; + + data = (unsigned char *)mlnds->pdu_base_offset; + data += pdu_offset; + + mlndo_hexfmt(data, n_bytes, swap_bytes, hexbuf, NDOBUFSZ); + + mlndo_printf(mlnds, ref, "get %d@%-3d = %s", + n_bytes, pdu_offset, hexbuf); + + if (!swap_bytes) + bcopy(data, buf, n_bytes); + else + mlnds_bswap(data, (unsigned char *)buf, n_bytes); + + return (1); +} + +/* + * mlndo_put_pdu + * + * This is a receiver makes right protocol. So we do not need + * to be concerned about the byte-order of an outgoing PDU. + */ +/*ARGSUSED*/ +static int +mlndo_put_pdu(struct mlndr_stream *mlnds, unsigned long pdu_offset, + unsigned long n_bytes, char *buf, int swap_bytes, + struct ndr_reference *ref) +{ + unsigned char *data; + char hexbuf[NDOBUFSZ]; + + data = (unsigned char *)mlnds->pdu_base_offset; + data += pdu_offset; + + mlndo_hexfmt((uint8_t *)buf, n_bytes, 0, hexbuf, NDOBUFSZ); + + mlndo_printf(mlnds, ref, "put %d@%-3d = %s", + n_bytes, pdu_offset, hexbuf); + + bcopy(buf, data, n_bytes); + return (1); +} + +static void +mlndo_tattle(struct mlndr_stream *mlnds, char *what, + struct ndr_reference *ref) +{ + mlndo_printf(mlnds, ref, what); +} + +static void +mlndo_tattle_error(struct mlndr_stream *mlnds, struct ndr_reference *ref) +{ + unsigned char *data; + char hexbuf[NDOBUFSZ]; + + data = (unsigned char *)mlnds->pdu_base_offset; + if (ref) + data += ref->pdu_offset; + else + data += mlnds->pdu_scan_offset; + + mlndo_hexfmt(data, 16, 0, hexbuf, NDOBUFSZ); + + mlndo_printf(mlnds, ref, "ERROR=%d REF=%d OFFSET=%d SIZE=%d/%d", + mlnds->error, mlnds->error_ref, mlnds->pdu_scan_offset, + mlnds->pdu_size, mlnds->pdu_max_size); + mlndo_printf(mlnds, ref, " %s", hexbuf); +} + +/* + * mlndo_reset + * + * Reset a stream: zap the outer_queue. We don't need to tamper + * with the stream heap: it's handled externally to the stream. + */ +static int +mlndo_reset(struct mlndr_stream *mlnds) +{ + mlndo_printf(mlnds, 0, "reset"); + + mlnds->pdu_size = 0; + mlnds->pdu_scan_offset = 0; + mlnds->outer_queue_head = 0; + mlnds->outer_current = 0; + mlnds->outer_queue_tailp = &mlnds->outer_queue_head; + + return (1); +} + +/* + * mlndo_destruct + * + * Destruct a stream: zap the outer_queue. Currently, this operation isn't + * required because the heap management (creation/destruction) is external + * to the stream but it provides a place-holder for stream cleanup. + */ +static void +mlndo_destruct(struct mlndr_stream *mlnds) +{ + mlndo_printf(mlnds, 0, "destruct"); + + if (mlnds->pdu_base_addr != 0) { + free(mlnds->pdu_base_addr); + mlnds->pdu_base_addr = 0; + mlnds->pdu_base_offset = 0; + } + + mlnds->outer_queue_head = 0; + mlnds->outer_current = 0; + mlnds->outer_queue_tailp = &mlnds->outer_queue_head; +} + +/* + * Printf style formatting for NDR operations. + */ +void +mlndo_printf(struct mlndr_stream *mlnds, struct ndr_reference *ref, + const char *fmt, ...) +{ + va_list ap; + char buf[NDOBUFSZ]; + + va_start(ap, fmt); + (void) vsnprintf(buf, NDOBUFSZ, fmt, ap); + va_end(ap); + + if (mlnds) + mlndo_fmt(mlnds, ref, buf); + else + mlndo_trace(buf); +} + +/* + * Main output formatter for NDR operations. + * + * UI 03 ... rpc_vers get 1@0 = 5 {05} + * UI 03 ... rpc_vers_minor get 1@1 = 0 {00} + * + * U Marshalling flag (M=marshal, U=unmarshal) + * I Direction flag (I=in, O=out) + * ... Field name + * get PDU operation (get or put) + * 1@0 Bytes @ offset (i.e. 1 byte at offset 0) + * {05} Value + */ +void +mlndo_fmt(struct mlndr_stream *mlnds, struct ndr_reference *ref, char *note) +{ + struct ndr_reference *p; + int indent; + char ref_name[NDOBUFSZ]; + char buf[NDOBUFSZ]; + int m_op_c = '?', dir_c = '?'; + + switch (mlnds->m_op) { + case 0: m_op_c = '-'; break; + case NDR_M_OP_MARSHALL: m_op_c = 'M'; break; + case NDR_M_OP_UNMARSHALL: m_op_c = 'U'; break; + default: m_op_c = '?'; break; + } + + switch (mlnds->dir) { + case 0: dir_c = '-'; break; + case NDR_DIR_IN: dir_c = 'I'; break; + case NDR_DIR_OUT: dir_c = 'O'; break; + default: dir_c = '?'; break; + } + + for (indent = 0, p = ref; p; p = p->enclosing) + indent++; + + if (ref && ref->name) { + if (*ref->name == '[' && ref->enclosing) { + indent--; + (void) snprintf(ref_name, NDOBUFSZ, "%s%s", + ref->enclosing->name, ref->name); + } else { + (void) strlcpy(ref_name, ref->name, NDOBUFSZ); + } + } else { + (void) strlcpy(ref_name, "----", NDOBUFSZ); + } + + (void) snprintf(buf, NDOBUFSZ, "%c%c %02d %-.*s %-*s %s", + m_op_c, dir_c, indent, indent, + "....+....+....+....+....+....", + 20 - indent, ref_name, note); + + mlndo_trace(buf); +} + +/*ARGSUSED*/ +void +mlndo_trace(const char *s) +{ + /* + * Temporary fbt for dtrace until user space sdt enabled. + */ +} + +/* + * Format data as hex bytes (limit is 10 bytes): + * + * 1188689424 {10 f6 d9 46} + * + * If the input data is greater than 10 bytes, an ellipsis will + * be inserted before the closing brace. + */ +static void +mlndo_hexfmt(uint8_t *data, int size, int swap_bytes, char *buf, int len) +{ + char *p = buf; + int interp = 1; + uint32_t c; + int n; + int i; + + n = (size > 10) ? 10 : size; + if (n > len-1) + n = len-1; + + switch (size) { + case 1: + c = *(uint8_t *)data; + break; + case 2: + if (swap_bytes == 0) /*LINTED E_BAD_PTR_CAST_ALIGN*/ + c = *(uint16_t *)data; + else + c = (data[0] << 8) | data[1]; + break; + case 4: + if (swap_bytes == 0) { /*LINTED E_BAD_PTR_CAST_ALIGN*/ + c = *(uint32_t *)data; + } else { + c = (data[0] << 24) | (data[1] << 16) + | (data[2] << 8) | data[3]; + } + break; + default: + c = 0; + interp = 0; + break; + } + + if (interp) + p += sprintf(p, "%4u {", c); + else + p += sprintf(p, " {"); + + p += sprintf(p, "%02x", data[0]); + for (i = 1; i < n; i++) + p += sprintf(p, " %02x", data[i]); + if (size > 10) + p += sprintf(p, " ...}"); + else + p += sprintf(p, "}"); + + /* + * Show c if it's a printable character or wide-char. + */ + if (size < 4 && isprint((uint8_t)c)) + (void) sprintf(p, " %c", (uint8_t)c); +} diff --git a/usr/src/lib/smbsrv/libmlrpc/common/mlndr.c b/usr/src/lib/smbsrv/libmlrpc/common/mlndr.c new file mode 100644 index 0000000000..6d13d459b3 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/mlndr.c @@ -0,0 +1,1965 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Network Data Representation (NDR) is a compatible subset of the DCE RPC + * and MSRPC NDR. NDR is used to move parameters consisting of + * complicated trees of data constructs between an RPC client and server. + */ + +#include <strings.h> +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/string.h> +#include <smbsrv/ndr.h> + +#define NDR_STRING_MAX 256 + +#define NDR_IS_UNION(T) \ + (((T)->type_flags & NDR_F_TYPEOP_MASK) == NDR_F_UNION) +#define NDR_IS_STRING(T) \ + (((T)->type_flags & NDR_F_TYPEOP_MASK) == NDR_F_STRING) + +extern struct ndr_typeinfo ndt_s_wchar; + +/* + * The following synopsis describes the terms TOP-MOST, OUTER and INNER. + * + * Each parameter (call arguments and return values) is a TOP-MOST item. + * A TOP-MOST item consists of one or more OUTER items. An OUTER item + * consists of one or more INNER items. There are important differences + * between each kind, which, primarily, have to do with the allocation + * of memory to contain data structures and the order of processing. + * + * This is most easily demonstrated with a short example. + * Consider these structures: + * + * struct top_param { + * long level; + * struct list * head; + * long count; + * }; + * + * struct list { + * struct list * next; + * char * str; // a string + * }; + * + * Now, consider an instance tree like this: + * + * +---------+ +-------+ +-------+ + * |top_param| +--->|list #1| +--->|list #2| + * +---------+ | +-------+ | +-------+ + * | level | | | next ----+ | next --->(NULL) + * | head ----+ | str -->"foo" | str -->"bar" + * | count | | flag | | flag | + * +---------+ +-------+ +-------+ + * + * The DCE(MS)/RPC Stub Data encoding for the tree is the following. + * The vertical bars (|) indicate OUTER construct boundaries. + * + * +-----+----------------------+----------------------+-----+-----+-----+ + * |level|#1.next #1.str #1.flag|#2.next #2.str #2.flag|"bar"|"foo"|count| + * +-----+----------------------+----------------------+-----+-----+-----+ + * level |<----------------------- head -------------------------->|count + * TOP TOP TOP + * + * Here's what to notice: + * + * - The members of the TOP-MOST construct are scattered through the Stub + * Data in the order they occur. This example shows a TOP-MOST construct + * consisting of atomic types (pointers and integers). A construct + * (struct) within the TOP-MOST construct would be contiguous and not + * scattered. + * + * - The members of OUTER constructs are contiguous, which allows for + * non-copied relocated (fixed-up) data structures at the packet's + * destination. We don't do fix-ups here. The pointers within the + * OUTER constructs are processed depth-first in the order that they + * occur. If they were processed breadth first, the sequence would + * be #1,"foo",#2,"bar". This is tricky because OUTER constructs may + * be variable length, and pointers are often encountered before the + * size(s) is known. + * + * - The INNER constructs are simply the members of an OUTER construct. + * + * For comparison, consider how ONC RPC would handle the same tree of + * data. ONC requires very little buffering, while DCE requires enough + * buffer space for the entire message. ONC does atom-by-atom depth-first + * (de)serialization and copy, while DCE allows for constructs to be + * "fixed-up" (relocated) in place at the destination. The packet data + * for the same tree processed by ONC RPC would look like this: + * + * +---------------------------------------------------------------------+ + * |level #1.next #2.next #2.str "bar" #2.flag #1.str "foo" #1.flag count| + * +---------------------------------------------------------------------+ + * TOP #1 #2 #2 bar #2 #1 foo #1 TOP + * + * More details about each TOP-MOST, OUTER, and INNER constructs appear + * throughout this source file near where such constructs are processed. + * + * NDR_REFERENCE + * + * The primary object for NDR is the struct ndr_reference. + * + * An ndr_reference indicates the local datum (i.e. native "C" data + * format), and the element within the Stub Data (contained within the + * RPC PDU (protocol data unit). An ndr_reference also indicates, + * largely as a debugging aid, something about the type of the + * element/datum, and the enclosing construct for the element. The + * ndr_reference's are typically allocated on the stack as locals, + * and the chain of ndr_reference.enclosing references is in reverse + * order of the call graph. + * + * The ndr_reference.datum is a pointer to the local memory that + * contains/receives the value. The ndr_reference.pdu_offset indicates + * where in the Stub Data the value is to be stored/retrieved. + * + * The ndr_reference also contains various parameters to the NDR + * process, such as ndr_reference.size_is, which indicates the size + * of variable length data, or ndr_reference.switch_is, which + * indicates the arm of a union to use. + * + * QUEUE OF OUTER REFERENCES + * + * Some OUTER constructs are variable size. Sometimes (often) we don't + * know the size of the OUTER construct until after pointers have been + * encountered. Hence, we can not begin processing the referent of the + * pointer until after the referring OUTER construct is completely + * processed, i.e. we don't know where to find/put the referent in the + * Stub Data until we know the size of all its predecessors. + * + * This is managed using the queue of OUTER references. The queue is + * anchored in mlndr_stream.outer_queue_head. At any time, + * mlndr_stream.outer_queue_tailp indicates where to put the + * ndr_reference for the next encountered pointer. + * + * Refer to the example above as we illustrate the queue here. In these + * illustrations, the queue entries are not the data structures themselves. + * Rather, they are ndr_reference entries which **refer** to the data + * structures in both the PDU and local memory. + * + * During some point in the processing, the queue looks like this: + * + * outer_current -------v + * outer_queue_head --> list#1 --0 + * outer_queue_tailp ---------& + * + * When the pointer #1.next is encountered, and entry is added to the + * queue, + * + * outer_current -------v + * outer_queue_head --> list#1 --> list#2 --0 + * outer_queue_tailp --------------------& + * + * and the members of #1 continue to be processed, which encounters + * #1.str: + * + * outer_current -------v + * outer_queue_head --> list#1 --> list#2 --> "foo" --0 + * outer_queue_tailp ------------------------------& + * + * Upon the completion of list#1, the processing continues by moving + * to mlndr_stream.outer_current->next, and the tail is set to this + * outer member: + * + * outer_current ------------------v + * outer_queue_head --> list#1 --> list#2 --> "foo" --0 + * outer_queue_tailp --------------------& + * + * Space for list#2 is allocated, either in the Stub Data or of local + * memory. When #2.next is encountered, it is found to be the null + * pointer and no reference is added to the queue. When #2.str is + * encountered, it is found to be valid, and a reference is added: + * + * outer_current ------------------v + * outer_queue_head --> list#1 --> list#2 --> "bar" --> "foo" --0 + * outer_queue_tailp ------------------------------& + * + * Processing continues in a similar fashion with the string "bar", + * which is variable-length. At this point, memory for "bar" may be + * malloc()ed during NDR_M_OP_UNMARSHALL: + * + * outer_current -----------------------------v + * outer_queue_head --> list#1 --> list#2 --> "bar" --> "foo" --0 + * outer_queue_tailp ------------------------------& + * + * And finishes on string "foo". Notice that because "bar" is a + * variable length string, and we don't know the PDU offset for "foo" + * until we reach this point. + * + * When the queue is drained (current->next==0), processing continues + * with the next TOP-MOST member. + * + * The queue of OUTER constructs manages the variable-length semantics + * of OUTER constructs and satisfies the depth-first requirement. + * We allow the queue to linger until the entire TOP-MOST structure is + * processed as an aid to debugging. + */ + +static struct ndr_reference *mlndr_enter_outer_queue(struct ndr_reference *); +extern int mlndr__ulong(struct ndr_reference *); + +/* + * TOP-MOST ELEMENTS + * + * This is fundamentally the first OUTER construct of the parameter, + * possibly followed by more OUTER constructs due to pointers. The + * datum (local memory) for TOP-MOST constructs (structs) is allocated + * by the caller of NDR. + * + * After the element is transferred, the outer_queue is drained. + * + * All we have to do is add an entry to the outer_queue for this + * top-most member, and commence the outer_queue processing. + */ +int +mlndo_process(struct mlndr_stream *mlnds, struct ndr_typeinfo *ti, + char *datum) +{ + struct ndr_reference myref; + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.datum = datum; + myref.name = "PROCESS"; + myref.ti = ti; + + return (mlndr_topmost(&myref)); +} + +int +mlndo_operation(struct mlndr_stream *mlnds, struct ndr_typeinfo *ti, + int opnum, char *datum) +{ + struct ndr_reference myref; + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.datum = datum; + myref.name = "OPERATION"; + myref.ti = ti; + myref.inner_flags = NDR_F_SWITCH_IS; + myref.switch_is = opnum; + + if (ti->type_flags != NDR_F_INTERFACE) { + NDR_SET_ERROR(&myref, NDR_ERR_NOT_AN_INTERFACE); + return (0); + } + + return ((*ti->ndr_func)(&myref)); +} + +int +mlndr_params(struct ndr_reference *params_ref) +{ + struct ndr_typeinfo *ti = params_ref->ti; + + if (ti->type_flags == NDR_F_OPERATION) + return (*ti->ndr_func) (params_ref); + else + return (mlndr_topmost(params_ref)); +} + +int +mlndr_topmost(struct ndr_reference *top_ref) +{ + struct mlndr_stream *mlnds; + struct ndr_typeinfo *ti; + struct ndr_reference *outer_ref = 0; + int is_varlen; + int is_string; + int error; + int rc; + unsigned n_fixed; + int params; + + assert(top_ref); + assert(top_ref->stream); + assert(top_ref->ti); + + mlnds = top_ref->stream; + ti = top_ref->ti; + + is_varlen = ti->pdu_size_variable_part; + is_string = NDR_IS_STRING(ti); + + assert(mlnds->outer_queue_tailp && !*mlnds->outer_queue_tailp); + assert(!mlnds->outer_current); + + params = top_ref->inner_flags & NDR_F_PARAMS_MASK; + + switch (params) { + case NDR_F_NONE: + case NDR_F_SWITCH_IS: + if (is_string || is_varlen) { + error = NDR_ERR_TOPMOST_VARLEN_ILLEGAL; + NDR_SET_ERROR(outer_ref, error); + return (0); + } + n_fixed = ti->pdu_size_fixed_part; + break; + + case NDR_F_SIZE_IS: + error = NDR_ERR_TOPMOST_VARLEN_ILLEGAL; + NDR_SET_ERROR(outer_ref, error); + return (0); + + case NDR_F_DIMENSION_IS: + if (is_varlen) { + error = NDR_ERR_ARRAY_VARLEN_ILLEGAL; + NDR_SET_ERROR(outer_ref, error); + return (0); + } + n_fixed = ti->pdu_size_fixed_part * top_ref->dimension_is; + break; + + case NDR_F_IS_POINTER: + case NDR_F_IS_POINTER+NDR_F_SIZE_IS: + n_fixed = 4; + break; + + case NDR_F_IS_REFERENCE: + case NDR_F_IS_REFERENCE+NDR_F_SIZE_IS: + n_fixed = 0; + break; + + default: + error = NDR_ERR_OUTER_PARAMS_BAD; + NDR_SET_ERROR(outer_ref, error); + return (0); + } + + outer_ref = mlndr_enter_outer_queue(top_ref); + if (!outer_ref) + return (0); /* error already set */ + + /* + * Hand-craft the first OUTER construct and directly call + * mlndr_inner(). Then, run the outer_queue. We do this + * because mlndr_outer() wants to malloc() memory for + * the construct, and we already have the memory. + */ + + /* move the flags, etc, around again, undoes enter_outer_queue() */ + outer_ref->inner_flags = top_ref->inner_flags; + outer_ref->outer_flags = 0; + outer_ref->datum = top_ref->datum; + + /* All outer constructs start on a mod4 (longword) boundary */ + if (!mlndr_outer_align(outer_ref)) + return (0); /* error already set */ + + /* Regardless of what it is, this is where it starts */ + outer_ref->pdu_offset = mlnds->pdu_scan_offset; + + rc = mlndr_outer_grow(outer_ref, n_fixed); + if (!rc) + return (0); /* error already set */ + + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_fixed; + + /* set-up outer_current, as though run_outer_queue() was doing it */ + mlnds->outer_current = outer_ref; + mlnds->outer_queue_tailp = &mlnds->outer_current->next; + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + + /* do the topmost member */ + rc = mlndr_inner(outer_ref); + if (!rc) + return (0); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + + /* advance, as though run_outer_queue() was doing it */ + mlnds->outer_current = mlnds->outer_current->next; + return (mlndr_run_outer_queue(mlnds)); +} + +static struct ndr_reference * +mlndr_enter_outer_queue(struct ndr_reference *arg_ref) +{ + struct mlndr_stream *mlnds = arg_ref->stream; + struct ndr_reference *outer_ref; + + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + outer_ref = (struct ndr_reference *) + MLNDS_MALLOC(mlnds, sizeof (*outer_ref), arg_ref); + if (!outer_ref) { + NDR_SET_ERROR(arg_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + + *outer_ref = *arg_ref; + + /* move advice in inner_flags to outer_flags */ + outer_ref->outer_flags = arg_ref->inner_flags & NDR_F_PARAMS_MASK; + outer_ref->inner_flags = 0; + outer_ref->enclosing = mlnds->outer_current; + outer_ref->backptr = 0; + outer_ref->datum = 0; + + assert(mlnds->outer_queue_tailp); + + outer_ref->next = *mlnds->outer_queue_tailp; + *mlnds->outer_queue_tailp = outer_ref; + mlnds->outer_queue_tailp = &outer_ref->next; + return (outer_ref); +} + +int +mlndr_run_outer_queue(struct mlndr_stream *mlnds) +{ + while (mlnds->outer_current) { + mlnds->outer_queue_tailp = &mlnds->outer_current->next; + + if (!mlndr_outer(mlnds->outer_current)) + return (0); + + mlnds->outer_current = mlnds->outer_current->next; + } + + return (1); +} + +/* + * OUTER CONSTRUCTS + * + * OUTER constructs are where the real work is, which stems from the + * variable-length potential. + * + * DCE(MS)/RPC VARIABLE LENGTH -- CONFORMANT, VARYING, VARYING/CONFORMANT + * + * DCE(MS)/RPC provides for three forms of variable length: CONFORMANT, + * VARYING, and VARYING/CONFORMANT. + * + * What makes this so tough is that the variable-length array may be well + * encapsulated within the outer construct. Further, because DCE(MS)/RPC + * tries to keep the constructs contiguous in the data stream, the sizing + * information precedes the entire OUTER construct. The sizing information + * must be used at the appropriate time, which can be after many, many, + * many fixed-length elements. During IDL type analysis, we know in + * advance constructs that encapsulate variable-length constructs. So, + * we know when we have a sizing header and when we don't. The actual + * semantics of the header are largely deferred. + * + * Currently, VARYING constructs are not implemented but they are described + * here in case they have to be implemented in the future. Similarly, + * DCE(MS)/RPC provides for multi-dimensional arrays, which are currently + * not implemented. Only one-dimensional, variable-length arrays are + * supported. + * + * CONFORMANT CONSTRUCTS -- VARIABLE LENGTH ARRAYS START THE SHOW + * + * All variable-length values are arrays. These arrays may be embedded + * well within another construct. However, a variable-length construct + * may ONLY appear as the last member of an enclosing construct. Example: + * + * struct credentials { + * ulong uid, gid; + * ulong n_gids; + * [size_is(n_gids)] + * ulong gids[*]; // variable-length. + * }; + * + * CONFORMANT constructs have a dynamic size in local memory and in the + * PDU. The CONFORMANT quality is indicated by the [size_is()] advice. + * CONFORMANT constructs have the following header: + * + * struct conformant_header { + * ulong size_is; + * }; + * + * (Multi-dimensional CONFORMANT arrays have a similar header for each + * dimension - not implemented). + * + * Example CONFORMANT construct: + * + * struct user { + * char * name; + * struct credentials cred; // see above + * }; + * + * Consider the data tree: + * + * +--------+ + * | user | + * +--------+ + * | name ----> "fred" (the string is a different OUTER) + * | uid | + * | gid | + * | n_gids | for example, 3 + * | gids[0]| + * | gids[1]| + * | gids[2]| + * +--------+ + * + * The OUTER construct in the Stub Data would be: + * + * +---+---------+---------------------------------------------+ + * |pad|size_is=3 name uid gid n_gids=3 gids[0] gids[1] gids[2]| + * +---+---------+---------------------------------------------+ + * szing hdr|user |<-------------- user.cred ------------>| + * |<--- fixed-size ---->|<----- conformant ---->| + * + * The ndr_typeinfo for struct user will have: + * pdu_fixed_size_part = 16 four long words (name uid gid n_gids) + * pdu_variable_size_part = 4 per element, sizeof gids[0] + * + * VARYING CONSTRUCTS -- NOT IMPLEMENTED + * + * VARYING constructs have the following header: + * + * struct varying_header { + * ulong first_is; + * ulong length_is; + * }; + * + * This indicates which interval of an array is significant. + * Non-intersecting elements of the array are undefined and usually + * zero-filled. The first_is parameter for C arrays is always 0 for + * the first element. + * + * N.B. Constructs may contain one CONFORMANT element, which is always + * last, but may contain many VARYING elements, which can be anywhere. + * + * VARYING CONFORMANT constructs have the sizing headers arranged like + * this: + * + * struct conformant_header all_conformant[N_CONFORMANT_DIM]; + * struct varying_header all_varying[N_VARYING_ELEMS_AND_DIMS]; + * + * The sizing header is immediately followed by the values for the + * construct. Again, we don't support more than one dimension and + * we don't support VARYING constructs at this time. + * + * A good example of a VARYING/CONFORMANT data structure is the UNIX + * directory entry: + * + * struct dirent { + * ushort reclen; + * ushort namlen; + * ulong inum; + * [size_is(reclen-8) length_is(namlen+1)] // -(2+2+4), +1 for NUL + * uchar name[*]; + * }; + * + * + * STRINGS ARE A SPECIAL CASE + * + * Strings are handled specially. MS/RPC uses VARYING/CONFORMANT structures + * for strings. This is a simple one-dimensional variable-length array, + * typically with its last element all zeroes. We handle strings with the + * header: + * + * struct string_header { + * ulong size_is; + * ulong first_is; // always 0 + * ulong length_is; // always same as size_is + * }; + * + * If general support for VARYING and VARYING/CONFORMANT mechanisms is + * implemented, we probably won't need the strings special case. + */ +int +mlndr_outer(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + int error = NDR_ERR_OUTER_PARAMS_BAD; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + NDR_TATTLE(outer_ref, "--OUTER--"); + + /* All outer constructs start on a mod4 (longword) boundary */ + if (!mlndr_outer_align(outer_ref)) + return (0); /* error already set */ + + /* Regardless of what it is, this is where it starts */ + outer_ref->pdu_offset = mlnds->pdu_scan_offset; + + if (is_union) { + error = NDR_ERR_OUTER_UNION_ILLEGAL; + NDR_SET_ERROR(outer_ref, error); + return (0); + } + + switch (params) { + case NDR_F_NONE: + if (is_string) + return (mlndr_outer_string(outer_ref)); + if (is_varlen) + return (mlndr_outer_conformant_construct(outer_ref)); + + return (mlndr_outer_fixed(outer_ref)); + break; + + case NDR_F_SIZE_IS: + case NDR_F_DIMENSION_IS: + if (is_varlen) { + error = NDR_ERR_ARRAY_VARLEN_ILLEGAL; + break; + } + + if (params == NDR_F_SIZE_IS) + return (mlndr_outer_conformant_array(outer_ref)); + else + return (mlndr_outer_fixed_array(outer_ref)); + break; + + default: + error = NDR_ERR_OUTER_PARAMS_BAD; + break; + } + + /* + * If we get here, something is wrong. Most likely, + * the params flags do not match. + */ + NDR_SET_ERROR(outer_ref, error); + return (0); +} + +int +mlndr_outer_fixed(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(!is_varlen && !is_string && !is_union); + assert(params == NDR_F_NONE); + + /* no header for this */ + n_hdr = 0; + + /* fixed part -- exactly one of these */ + n_fixed = ti->pdu_size_fixed_part; + assert(n_fixed > 0); + + /* variable part -- exactly none of these */ + n_variable = 0; + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + valp = outer_ref->datum; + assert(valp); + if (outer_ref->backptr) { + assert(valp == *outer_ref->backptr); + } + break; + + case NDR_M_OP_UNMARSHALL: + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "FIXED-VALUE"; + myref.outer_flags = NDR_F_NONE; + myref.inner_flags = NDR_F_NONE; + + myref.pdu_offset = outer_ref->pdu_offset; + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_pdu_total; + + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); +} + +int +mlndr_outer_fixed_array(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(!is_varlen && !is_string && !is_union); + assert(params == NDR_F_DIMENSION_IS); + + /* no header for this */ + n_hdr = 0; + + /* fixed part -- exactly dimension_is of these */ + n_fixed = ti->pdu_size_fixed_part * outer_ref->dimension_is; + assert(n_fixed > 0); + + /* variable part -- exactly none of these */ + n_variable = 0; + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + valp = outer_ref->datum; + assert(valp); + if (outer_ref->backptr) { + assert(valp == *outer_ref->backptr); + } + break; + + case NDR_M_OP_UNMARSHALL: + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "FIXED-ARRAY"; + myref.outer_flags = NDR_F_NONE; + myref.inner_flags = NDR_F_DIMENSION_IS; + myref.dimension_is = outer_ref->dimension_is; + + myref.pdu_offset = outer_ref->pdu_offset; + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_pdu_total; + + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); +} + +int +mlndr_outer_conformant_array(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + unsigned long size_is; + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(!is_varlen && !is_string && !is_union); + assert(params == NDR_F_SIZE_IS); + + /* conformant header for this */ + n_hdr = 4; + + /* fixed part -- exactly none of these */ + n_fixed = 0; + + /* variable part -- exactly size_of of these */ + /* notice that it is the **fixed** size of the ti */ + n_variable = ti->pdu_size_fixed_part * outer_ref->size_is; + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + size_is = outer_ref->size_is; + rc = mlndr_outer_poke_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + + valp = outer_ref->datum; + assert(valp); + if (outer_ref->backptr) { + assert(valp == *outer_ref->backptr); + } + break; + + case NDR_M_OP_UNMARSHALL: + rc = mlndr_outer_peek_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + + if (size_is != outer_ref->size_is) { + NDR_SET_ERROR(outer_ref, + NDR_ERR_SIZE_IS_MISMATCH_PDU); + return (0); + } + + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "CONFORMANT-ARRAY"; + myref.outer_flags = NDR_F_NONE; + myref.inner_flags = NDR_F_SIZE_IS; + myref.size_is = outer_ref->size_is; + + myref.inner_flags = NDR_F_DIMENSION_IS; /* convenient */ + myref.dimension_is = outer_ref->size_is; /* convenient */ + + myref.pdu_offset = outer_ref->pdu_offset + 4; + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_pdu_total; + + outer_ref->type_flags = NDR_F_NONE; + outer_ref->inner_flags = NDR_F_NONE; + + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); +} + +int +mlndr_outer_conformant_construct(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + unsigned long size_is; + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(is_varlen && !is_string && !is_union); + assert(params == NDR_F_NONE); + + /* conformant header for this */ + n_hdr = 4; + + /* fixed part -- exactly one of these */ + n_fixed = ti->pdu_size_fixed_part; + + /* variable part -- exactly size_of of these */ + n_variable = 0; /* 0 for the moment */ + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + /* For the moment, grow enough for the fixed-size part */ + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + /* + * We don't know the size yet. We have to wait for + * it. Proceed with the fixed-size part, and await + * the call to mlndr_size_is(). + */ + size_is = 0; + rc = mlndr_outer_poke_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + + valp = outer_ref->datum; + assert(valp); + if (outer_ref->backptr) { + assert(valp == *outer_ref->backptr); + } + break; + + case NDR_M_OP_UNMARSHALL: + /* + * We know the size of the variable part because + * of the CONFORMANT header. We will verify + * the header against the [size_is(X)] advice + * later when mlndr_size_is() is called. + */ + rc = mlndr_outer_peek_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + + /* recalculate metrics */ + n_variable = size_is * ti->pdu_size_variable_part; + n_pdu_total = n_hdr + n_fixed + n_variable; + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + outer_ref->size_is = size_is; /* verified later */ + + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "CONFORMANT-CONSTRUCT"; + myref.outer_flags = NDR_F_NONE; + myref.inner_flags = NDR_F_NONE; + myref.size_is = outer_ref->size_is; + + myref.pdu_offset = outer_ref->pdu_offset + 4; + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_pdu_total; + + outer_ref->type_flags = NDR_F_SIZE_IS; /* indicate pending */ + outer_ref->inner_flags = NDR_F_NONE; /* indicate pending */ + + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + + if (outer_ref->inner_flags != NDR_F_SIZE_IS) { + NDR_SET_ERROR(&myref, NDR_ERR_SIZE_IS_MISMATCH_AFTER); + return (0); + } + + return (1); +} + +int +mlndr_size_is(struct ndr_reference *ref) +{ + struct mlndr_stream *mlnds = ref->stream; + struct ndr_reference *outer_ref = mlnds->outer_current; + struct ndr_typeinfo *ti = outer_ref->ti; + unsigned long size_is; + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_pdu_total; + + assert(ref->inner_flags & NDR_F_SIZE_IS); + size_is = ref->size_is; + + if (outer_ref->type_flags != NDR_F_SIZE_IS) { + NDR_SET_ERROR(ref, NDR_ERR_SIZE_IS_UNEXPECTED); + return (0); + } + + if (outer_ref->inner_flags & NDR_F_SIZE_IS) { + NDR_SET_ERROR(ref, NDR_ERR_SIZE_IS_DUPLICATED); + return (0); + } + + /* repeat metrics, see mlndr_conformant_construct() above */ + n_hdr = 4; + n_fixed = ti->pdu_size_fixed_part; + n_variable = size_is * ti->pdu_size_variable_part; + n_pdu_total = n_hdr + n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + /* + * We have to set the sizing header and extend + * the size of the PDU (already done). + */ + rc = mlndr_outer_poke_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + break; + + case NDR_M_OP_UNMARSHALL: + /* + * Allocation done during mlndr_conformant_construct(). + * All we are doing here is verifying that the + * intended size (ref->size_is) matches the sizing header. + */ + if (size_is != outer_ref->size_is) { + NDR_SET_ERROR(ref, NDR_ERR_SIZE_IS_MISMATCH_PDU); + return (0); + } + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + outer_ref->inner_flags |= NDR_F_SIZE_IS; + outer_ref->size_is = ref->size_is; + return (1); +} + +int +mlndr_outer_string(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + unsigned is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + int rc; + unsigned n_zeroes; + unsigned ix; + unsigned long size_is; + unsigned long first_is; + unsigned long length_is; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(is_varlen && is_string && !is_union); + assert(params == NDR_F_NONE); + + /* string header for this: size_is first_is length_is */ + n_hdr = 12; + + /* fixed part -- exactly none of these */ + n_fixed = 0; + + if (!mlndr_outer_grow(outer_ref, n_hdr)) + return (0); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + valp = outer_ref->datum; + assert(valp); + + if (outer_ref->backptr) + assert(valp == *outer_ref->backptr); + + if (ti == &ndt_s_wchar) { + /* + * size_is is the number of characters in the string, + * including the null. We assume valp is UTF-8 encoded. + * We can use mts_wcequiv_strlen for ASCII, extended + * ASCII or Unicode (UCS-2). + */ + size_is = (mts_wcequiv_strlen(valp) / + sizeof (mts_wchar_t)) + 1; + + if (size_is > NDR_STRING_MAX) { + NDR_SET_ERROR(outer_ref, NDR_ERR_STRLEN); + return (0); + } + } else { + valp = outer_ref->datum; + n_zeroes = 0; + for (ix = 0; ix < 1024; ix++) { + if (valp[ix] == 0) { + n_zeroes++; + if (n_zeroes >= is_varlen && + ix % is_varlen == 0) { + break; + } + } else { + n_zeroes = 0; + } + } + if (ix >= 1024) { + NDR_SET_ERROR(outer_ref, NDR_ERR_STRLEN); + return (0); + } + size_is = ix+1; + } + + first_is = 0; + length_is = size_is; + + if (!mlndr_outer_poke_sizing(outer_ref, 0, &size_is) || + !mlndr_outer_poke_sizing(outer_ref, 4, &first_is) || + !mlndr_outer_poke_sizing(outer_ref, 8, &length_is)) + return (0); /* error already set */ + break; + + case NDR_M_OP_UNMARSHALL: + if (!mlndr_outer_peek_sizing(outer_ref, 0, &size_is) || + !mlndr_outer_peek_sizing(outer_ref, 4, &first_is) || + !mlndr_outer_peek_sizing(outer_ref, 8, &length_is)) + return (0); /* error already set */ + + /* + * In addition to the first_is check, we used to check that + * size_is or size_is-1 was equal to length_is but Windows95 + * doesn't conform to this "rule" (see variable part below). + * The srvmgr tool for Windows95 sent the following values + * for a path string: + * + * size_is = 261 (0x105) + * first_is = 0 + * length_is = 53 (0x35) + * + * The length_is was correct (for the given path) but the + * size_is was the maximum path length rather than being + * related to length_is. + */ + if (first_is != 0) { + NDR_SET_ERROR(outer_ref, NDR_ERR_STRING_SIZING); + return (0); + } + + if (ti == &ndt_s_wchar) { + /* + * Decoding Unicode to UTF-8; we need to allow + * for the maximum possible char size. It would + * be nice to use mbequiv_strlen but the string + * may not be null terminated. + */ + n_alloc = (size_is + 1) * MTS_MB_CHAR_MAX; + } else { + n_alloc = (size_is + 1) * is_varlen; + } + + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + + bzero(valp, (size_is+1) * is_varlen); + + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + /* + * Variable part - exactly length_is of these. + * + * Usually, length_is is same as size_is and includes nul. + * Some protocols use length_is = size_is-1, and length_is does + * not include the nul (which is more consistent with DCE spec). + * If the length_is is 0, there is no data following the + * sizing header, regardless of size_is. + */ + n_variable = length_is * is_varlen; + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + if (length_is > 0) { + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "OUTER-STRING"; + myref.outer_flags = NDR_F_IS_STRING; + myref.inner_flags = NDR_F_NONE; + + /* + * Set up strlen_is so that we know what to + * expect later (see mlndr_s_wchar). + */ + myref.strlen_is = length_is; + } + + myref.pdu_offset = outer_ref->pdu_offset + 12; + + /* + * Don't try to decode empty strings. + */ + if ((size_is == 0) && (first_is == 0) && (length_is == 0)) { + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); + } + + if ((size_is != 0) && (length_is != 0)) { + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + } + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); +} + +int +mlndr_outer_peek_sizing(struct ndr_reference *outer_ref, unsigned offset, + unsigned long *sizing_p) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + unsigned long pdu_offset; + int rc; + + pdu_offset = outer_ref->pdu_offset + offset; + + if (pdu_offset < mlnds->outer_current->pdu_offset || + pdu_offset > mlnds->outer_current->pdu_end_offset || + pdu_offset+4 > mlnds->outer_current->pdu_end_offset) { + NDR_SET_ERROR(outer_ref, NDR_ERR_BOUNDS_CHECK); + return (0); + } + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + NDR_SET_ERROR(outer_ref, NDR_ERR_UNIMPLEMENTED); + return (0); + + case NDR_M_OP_UNMARSHALL: + rc = MLNDS_GET_PDU(mlnds, pdu_offset, 4, (char *)sizing_p, + mlnds->swap, outer_ref); + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + return (rc); +} + +int +mlndr_outer_poke_sizing(struct ndr_reference *outer_ref, unsigned offset, + unsigned long *sizing_p) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + unsigned long pdu_offset; + int rc; + + pdu_offset = outer_ref->pdu_offset + offset; + + if (pdu_offset < mlnds->outer_current->pdu_offset || + pdu_offset > mlnds->outer_current->pdu_end_offset || + pdu_offset+4 > mlnds->outer_current->pdu_end_offset) { + NDR_SET_ERROR(outer_ref, NDR_ERR_BOUNDS_CHECK); + return (0); + } + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + rc = MLNDS_PUT_PDU(mlnds, pdu_offset, 4, (char *)sizing_p, + mlnds->swap, outer_ref); + break; + + case NDR_M_OP_UNMARSHALL: + NDR_SET_ERROR(outer_ref, NDR_ERR_UNIMPLEMENTED); + return (0); + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + return (rc); +} + +/* + * All OUTER constructs begin on a mod4 (dword) boundary - except + * for the ones that don't: some MSRPC calls appear to use word or + * packed alignment. Strings appear to be dword aligned. + */ +int +mlndr_outer_align(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + int rc; + unsigned n_pad; + unsigned align; + + if (outer_ref->packed_alignment && outer_ref->ti != &ndt_s_wchar) { + align = outer_ref->ti->alignment; + n_pad = ((align + 1) - mlnds->pdu_scan_offset) & align; + } else { + n_pad = (4 - mlnds->pdu_scan_offset) & 3; + } + + if (n_pad == 0) + return (1); /* already aligned, often the case */ + + if (!mlndr_outer_grow(outer_ref, n_pad)) + return (0); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + rc = MLNDS_PAD_PDU(mlnds, + mlnds->pdu_scan_offset, n_pad, outer_ref); + if (!rc) { + NDR_SET_ERROR(outer_ref, NDR_ERR_PAD_FAILED); + return (0); + } + break; + + case NDR_M_OP_UNMARSHALL: + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + mlnds->pdu_scan_offset += n_pad; + return (1); +} + +int +mlndr_outer_grow(struct ndr_reference *outer_ref, unsigned n_total) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + unsigned long pdu_want_size; + int rc, is_ok = 0; + + pdu_want_size = mlnds->pdu_scan_offset + n_total; + + if (pdu_want_size <= mlnds->pdu_max_size) { + is_ok = 1; + } + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + if (is_ok) + break; + rc = MLNDS_GROW_PDU(mlnds, pdu_want_size, outer_ref); + if (!rc) { + NDR_SET_ERROR(outer_ref, NDR_ERR_GROW_FAILED); + return (0); + } + break; + + case NDR_M_OP_UNMARSHALL: + if (is_ok) + break; + NDR_SET_ERROR(outer_ref, NDR_ERR_UNDERFLOW); + return (0); + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + if (mlnds->pdu_size < pdu_want_size) + mlnds->pdu_size = pdu_want_size; + + outer_ref->pdu_end_offset = pdu_want_size; + return (1); +} + +/* + * INNER ELEMENTS + * + * The local datum (arg_ref->datum) already exists, there is no need to + * malloc() it. The datum should point at a member of a structure. + * + * For the most part, mlndr_inner() and its helpers are just a sanity + * check. The underlying ti->ndr_func() could be called immediately + * for non-pointer elements. For the sake of robustness, we detect + * run-time errors here. Most of the situations this protects against + * have already been checked by the IDL compiler. This is also a + * common point for processing of all data, and so is a convenient + * place to work from for debugging. + */ +int +mlndr_inner(struct ndr_reference *arg_ref) +{ + struct ndr_typeinfo *ti = arg_ref->ti; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int error = NDR_ERR_INNER_PARAMS_BAD; + int params; + + params = arg_ref->inner_flags & NDR_F_PARAMS_MASK; + + switch (params) { + case NDR_F_NONE: + if (is_union) { + error = NDR_ERR_SWITCH_VALUE_MISSING; + break; + } + return (*ti->ndr_func)(arg_ref); + break; + + case NDR_F_SIZE_IS: + case NDR_F_DIMENSION_IS: + case NDR_F_IS_POINTER+NDR_F_SIZE_IS: /* pointer to something */ + case NDR_F_IS_REFERENCE+NDR_F_SIZE_IS: /* pointer to something */ + if (is_varlen) { + error = NDR_ERR_ARRAY_VARLEN_ILLEGAL; + break; + } + if (is_union) { + error = NDR_ERR_ARRAY_UNION_ILLEGAL; + break; + } + if (params & NDR_F_IS_POINTER) + return (mlndr_inner_pointer(arg_ref)); + else if (params & NDR_F_IS_REFERENCE) + return (mlndr_inner_reference(arg_ref)); + else + return (mlndr_inner_array(arg_ref)); + break; + + case NDR_F_IS_POINTER: /* type is pointer to one something */ + if (is_union) { + error = NDR_ERR_ARRAY_UNION_ILLEGAL; + break; + } + return (mlndr_inner_pointer(arg_ref)); + break; + + case NDR_F_IS_REFERENCE: /* type is pointer to one something */ + if (is_union) { + error = NDR_ERR_ARRAY_UNION_ILLEGAL; + break; + } + return (mlndr_inner_reference(arg_ref)); + break; + + case NDR_F_SWITCH_IS: + if (!is_union) { + error = NDR_ERR_SWITCH_VALUE_ILLEGAL; + break; + } + return (*ti->ndr_func)(arg_ref); + break; + + default: + error = NDR_ERR_INNER_PARAMS_BAD; + break; + } + + /* + * If we get here, something is wrong. Most likely, + * the params flags do not match + */ + NDR_SET_ERROR(arg_ref, error); + return (0); +} + +int +mlndr_inner_pointer(struct ndr_reference *arg_ref) +{ + struct mlndr_stream *mlnds = arg_ref->stream; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + char **valpp = (char **)arg_ref->datum; + struct ndr_reference *outer_ref; + + if (!mlndr__ulong(arg_ref)) + return (0); /* error */ + if (!*valpp) + return (1); /* NULL pointer */ + + outer_ref = mlndr_enter_outer_queue(arg_ref); + if (!outer_ref) + return (0); /* error already set */ + + /* move advice in inner_flags to outer_flags sans pointer */ + outer_ref->outer_flags = arg_ref->inner_flags & NDR_F_PARAMS_MASK; + outer_ref->outer_flags &= ~NDR_F_IS_POINTER; +#ifdef NDR_INNER_NOT_YET + outer_ref->outer_flags |= NDR_F_BACKPTR; + if (outer_ref->outer_flags & NDR_F_SIZE_IS) { + outer_ref->outer_flags |= NDR_F_ARRAY+NDR_F_CONFORMANT; + } +#endif /* NDR_INNER_NOT_YET */ + + outer_ref->backptr = valpp; + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + outer_ref->datum = *valpp; + break; + + case NDR_M_OP_UNMARSHALL: + /* + * This is probably wrong if the application allocated + * memory in advance. Indicate no value for now. + * ONC RPC handles this case. + */ + *valpp = 0; + outer_ref->datum = 0; + break; + } + + return (1); /* pointer dereference scheduled */ +} + +int +mlndr_inner_reference(struct ndr_reference *arg_ref) +{ + struct mlndr_stream *mlnds = arg_ref->stream; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + char **valpp = (char **)arg_ref->datum; + struct ndr_reference *outer_ref; + + outer_ref = mlndr_enter_outer_queue(arg_ref); + if (!outer_ref) + return (0); /* error already set */ + + /* move advice in inner_flags to outer_flags sans pointer */ + outer_ref->outer_flags = arg_ref->inner_flags & NDR_F_PARAMS_MASK; + outer_ref->outer_flags &= ~NDR_F_IS_REFERENCE; +#ifdef NDR_INNER_REF_NOT_YET + outer_ref->outer_flags |= NDR_F_BACKPTR; + if (outer_ref->outer_flags & NDR_F_SIZE_IS) { + outer_ref->outer_flags |= NDR_F_ARRAY+NDR_F_CONFORMANT; + } +#endif /* NDR_INNER_REF_NOT_YET */ + + outer_ref->backptr = valpp; + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + outer_ref->datum = *valpp; + break; + + case NDR_M_OP_UNMARSHALL: + /* + * This is probably wrong if the application allocated + * memory in advance. Indicate no value for now. + * ONC RPC handles this case. + */ + *valpp = 0; + outer_ref->datum = 0; + break; + } + + return (1); /* pointer dereference scheduled */ +} + +int +mlndr_inner_array(struct ndr_reference *encl_ref) +{ + struct ndr_typeinfo *ti = encl_ref->ti; + struct ndr_reference myref; + unsigned long pdu_offset = encl_ref->pdu_offset; + unsigned long n_elem; + unsigned long i; + char name[30]; + + if (encl_ref->inner_flags & NDR_F_SIZE_IS) { + /* now is the time to check/set size */ + if (!mlndr_size_is(encl_ref)) + return (0); /* error already set */ + n_elem = encl_ref->size_is; + } else { + assert(encl_ref->inner_flags & NDR_F_DIMENSION_IS); + n_elem = encl_ref->dimension_is; + } + + bzero(&myref, sizeof (myref)); + myref.enclosing = encl_ref; + myref.stream = encl_ref->stream; + myref.packed_alignment = 0; + myref.ti = ti; + myref.inner_flags = NDR_F_NONE; + + for (i = 0; i < n_elem; i++) { + (void) sprintf(name, "[%lu]", i); + myref.name = name; + myref.pdu_offset = pdu_offset + i * ti->pdu_size_fixed_part; + myref.datum = encl_ref->datum + i * ti->c_size_fixed_part; + + if (!mlndr_inner(&myref)) + return (0); + } + + return (1); +} + + +/* + * BASIC TYPES + */ +#define MAKE_BASIC_TYPE_BASE(TYPE, SIZE) \ + extern int mlndr_##TYPE(struct ndr_reference *encl_ref); \ + struct ndr_typeinfo ndt_##TYPE = { \ + 1, /* NDR version */ \ + (SIZE)-1, /* alignment */ \ + NDR_F_NONE, /* flags */ \ + mlndr_##TYPE, /* ndr_func */ \ + SIZE, /* pdu_size_fixed_part */ \ + 0, /* pdu_size_variable_part */ \ + SIZE, /* c_size_fixed_part */ \ + 0, /* c_size_variable_part */ \ + }; \ + int mlndr_##TYPE(struct ndr_reference *ref) { \ + return (mlndr_basic_integer(ref, SIZE)); \ +} + +#define MAKE_BASIC_TYPE_STRING(TYPE, SIZE) \ + extern int mlndr_s##TYPE(struct ndr_reference *encl_ref); \ + struct ndr_typeinfo ndt_s##TYPE = { \ + 1, /* NDR version */ \ + (SIZE)-1, /* alignment */ \ + NDR_F_STRING, /* flags */ \ + mlndr_s##TYPE, /* ndr_func */ \ + 0, /* pdu_size_fixed_part */ \ + SIZE, /* pdu_size_variable_part */ \ + 0, /* c_size_fixed_part */ \ + SIZE, /* c_size_variable_part */ \ + }; \ + int mlndr_s##TYPE(struct ndr_reference *ref) { \ + return (mlndr_string_basic_integer(ref, &ndt_##TYPE)); \ +} + +#define MAKE_BASIC_TYPE(TYPE, SIZE) \ + MAKE_BASIC_TYPE_BASE(TYPE, SIZE) \ + MAKE_BASIC_TYPE_STRING(TYPE, SIZE) + +extern int +mlndr_basic_integer(struct ndr_reference *ref, unsigned size); + +extern int +mlndr_string_basic_integer(struct ndr_reference *encl_ref, + struct ndr_typeinfo *type_under); + + +MAKE_BASIC_TYPE(_char, 1) +MAKE_BASIC_TYPE(_uchar, 1) +MAKE_BASIC_TYPE(_short, 2) +MAKE_BASIC_TYPE(_ushort, 2) +MAKE_BASIC_TYPE(_long, 4) +MAKE_BASIC_TYPE(_ulong, 4) + +MAKE_BASIC_TYPE_BASE(_wchar, 2) + +int +mlndr_basic_integer(struct ndr_reference *ref, unsigned size) +{ + struct mlndr_stream *mlnds = ref->stream; + char *valp = (char *)ref->datum; + int rc; + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + rc = MLNDS_PUT_PDU(mlnds, ref->pdu_offset, size, + valp, mlnds->swap, ref); + break; + + case NDR_M_OP_UNMARSHALL: + rc = MLNDS_GET_PDU(mlnds, ref->pdu_offset, size, + valp, mlnds->swap, ref); + break; + + default: + NDR_SET_ERROR(ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + return (rc); +} + +int +mlndr_string_basic_integer(struct ndr_reference *encl_ref, + struct ndr_typeinfo *type_under) +{ + unsigned long pdu_offset = encl_ref->pdu_offset; + unsigned size = type_under->pdu_size_fixed_part; + char *valp; + struct ndr_reference myref; + unsigned long i; + long sense = 0; + char name[30]; + + assert(size != 0); + + bzero(&myref, sizeof (myref)); + myref.enclosing = encl_ref; + myref.stream = encl_ref->stream; + myref.packed_alignment = 0; + myref.ti = type_under; + myref.inner_flags = NDR_F_NONE; + myref.name = name; + + for (i = 0; i < NDR_STRING_MAX; i++) { + (void) sprintf(name, "[%lu]", i); + myref.pdu_offset = pdu_offset + i * size; + valp = encl_ref->datum + i * size; + myref.datum = valp; + + if (!mlndr_inner(&myref)) + return (0); + + switch (size) { + case 1: sense = *valp; break; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + case 2: sense = *(short *)valp; break; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + case 4: sense = *(long *)valp; break; + } + + if (!sense) + break; + } + + return (1); +} + + +extern int mlndr_s_wchar(struct ndr_reference *encl_ref); +struct ndr_typeinfo ndt_s_wchar = { + 1, /* NDR version */ + 2-1, /* alignment */ + NDR_F_STRING, /* flags */ + mlndr_s_wchar, /* ndr_func */ + 0, /* pdu_size_fixed_part */ + 2, /* pdu_size_variable_part */ + 0, /* c_size_fixed_part */ + 1, /* c_size_variable_part */ +}; + + +/* + * Hand coded wchar function because all strings are transported + * as wide characters. During NDR_M_OP_MARSHALL, we convert from + * multi-byte to wide characters. During NDR_M_OP_UNMARSHALL, we + * convert from wide characters to multi-byte. + * + * It appeared that NT would sometimes leave a spurious character + * in the data stream before the null wide_char, which would get + * included in the string decode because we processed until the + * null character. It now looks like NT does not always terminate + * RPC Unicode strings and the terminating null is a side effect + * of field alignment. So now we rely on the strlen_is (set up in + * mlndr_outer_string) of the enclosing reference. This may or may + * not include the null but it doesn't matter, the algorithm will + * get it right. + */ +int +mlndr_s_wchar(struct ndr_reference *encl_ref) +{ + struct mlndr_stream *mlnds = encl_ref->stream; + unsigned short wide_char; + char *valp; + struct ndr_reference myref; + unsigned long i; + char name[30]; + int count; + int char_count = 0; + + if (mlnds->m_op == NDR_M_OP_UNMARSHALL) { + /* + * To avoid problems with zero length strings + * we can just null terminate here and be done. + */ + if (encl_ref->strlen_is == 0) { + encl_ref->datum[0] = '\0'; + return (1); + } + } + + bzero(&myref, sizeof (myref)); + myref.enclosing = encl_ref; + myref.stream = encl_ref->stream; + myref.packed_alignment = 0; + myref.ti = &ndt__wchar; + myref.inner_flags = NDR_F_NONE; + myref.datum = (char *)&wide_char; + myref.name = name; + myref.pdu_offset = encl_ref->pdu_offset; + + valp = encl_ref->datum; + count = 0; + + for (i = 0; i < NDR_STRING_MAX; i++) { + (void) sprintf(name, "[%lu]", i); + + if (mlnds->m_op == NDR_M_OP_MARSHALL) { + count = mts_mbtowc((mts_wchar_t *)&wide_char, valp, + MTS_MB_CHAR_MAX); + if (count < 0) { + return (0); + } else if (count == 0) { + /* + * If the input char is 0, mts_mbtowc + * returns 0 without setting wide_char. + * I'm not sure if this is the correct + * behaviour for mts_mbtowc but for + * now we need to set wide_char to 0 + * and assume a count of 1. + */ + wide_char = *valp; + count = 1; + } + } + + if (!mlndr_inner(&myref)) + return (0); + + if (mlnds->m_op == NDR_M_OP_UNMARSHALL) { + count = mts_wctomb(valp, wide_char); + + if ((++char_count) == encl_ref->strlen_is) { + valp += count; + *valp = '\0'; + break; + } + } + + if (!wide_char) + break; + + myref.pdu_offset += sizeof (wide_char); + valp += count; + } + + return (1); +} diff --git a/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_client.c b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_client.c new file mode 100644 index 0000000000..9bce9469ea --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_client.c @@ -0,0 +1,369 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/errno.h> +#include <string.h> +#include <strings.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ndr.h> +#include <smbsrv/mlrpc.h> + +#define MLRPC_IS_LAST_FRAG(F) ((F) & MLRPC_PFC_LAST_FRAG) +#define MLRPC_DEFAULT_FRAGSZ 8192 + +static void mlrpc_c_init_hdr(struct mlrpc_client *, struct mlrpc_xaction *); +static int mlrpc_c_get_frags(struct mlrpc_client *, struct mlrpc_xaction *); +static void mlrpc_c_remove_hdr(struct mlndr_stream *, int *); + +int +mlrpc_c_bind(struct mlrpc_client *mcli, char *service_name, + struct mlrpc_binding **ret_binding_p) +{ + struct mlrpc_service *msvc; + struct mlrpc_binding *mbind; + struct mlrpc_xaction mxa; + mlrpcconn_bind_hdr_t *bhdr; + mlrpc_p_cont_elem_t *pce; + mlrpcconn_bind_ack_hdr_t *bahdr; + mlrpc_p_result_t *pre; + int rc; + + bzero(&mxa, sizeof (mxa)); + + msvc = mlrpc_find_service_by_name(service_name); + if (msvc == NULL) + return (MLRPC_DRC_FAULT_API_SERVICE_INVALID); + + mxa.binding_list = mcli->binding_list; + if ((mbind = mlrpc_new_binding(&mxa)) == NULL) + return (MLRPC_DRC_FAULT_API_BIND_NO_SLOTS); + + mlrpc_c_init_hdr(mcli, &mxa); + + bhdr = &mxa.send_hdr.bind_hdr; + bhdr->common_hdr.ptype = MLRPC_PTYPE_BIND; + bhdr->common_hdr.frag_length = sizeof (*bhdr); + bhdr->max_xmit_frag = MLRPC_DEFAULT_FRAGSZ; + bhdr->max_recv_frag = MLRPC_DEFAULT_FRAGSZ; + bhdr->assoc_group_id = 0; + bhdr->p_context_elem.n_context_elem = 1; + + /* Assign presentation context id */ + pce = &bhdr->p_context_elem.p_cont_elem[0]; + pce->p_cont_id = mcli->next_p_cont_id++; + pce->n_transfer_syn = 1; + + /* Set up UUIDs and versions from the service */ + pce->abstract_syntax.if_version = msvc->abstract_syntax_version; + rc = mlrpc_str_to_uuid(msvc->abstract_syntax_uuid, + &pce->abstract_syntax.if_uuid); + if (!rc) + return (MLRPC_DRC_FAULT_API_SERVICE_INVALID); + + pce->transfer_syntaxes[0].if_version = msvc->transfer_syntax_version; + rc = mlrpc_str_to_uuid(msvc->transfer_syntax_uuid, + &pce->transfer_syntaxes[0].if_uuid); + if (!rc) + return (MLRPC_DRC_FAULT_API_SERVICE_INVALID); + + /* Format and exchange the PDU */ + + rc = (*mcli->xa_init)(mcli, &mxa, 0); + if (MLRPC_DRC_IS_FAULT(rc)) + return (rc); + + rc = mlrpc_encode_pdu_hdr(&mxa); + if (MLRPC_DRC_IS_FAULT(rc)) + goto fault_exit; + + rc = (*mcli->xa_exchange)(mcli, &mxa); + if (MLRPC_DRC_IS_FAULT(rc)) + goto fault_exit; + + rc = mlrpc_decode_pdu_hdr(&mxa); + if (MLRPC_DRC_IS_FAULT(rc)) + goto fault_exit; + + /* done with buffers */ + (*mcli->xa_destruct)(mcli, &mxa); + + bahdr = &mxa.recv_hdr.bind_ack_hdr; + + if (mxa.ptype != MLRPC_PTYPE_BIND_ACK) + return (MLRPC_DRC_FAULT_RECEIVED_MALFORMED); + + if (bahdr->p_result_list.n_results != 1) + return (MLRPC_DRC_FAULT_RECEIVED_MALFORMED); + + pre = &bahdr->p_result_list.p_results[0]; + + if (pre->result != MLRPC_PCDR_ACCEPTANCE) + return (MLRPC_DRC_FAULT_RECEIVED_MALFORMED); + + mbind->p_cont_id = pce->p_cont_id; + mbind->which_side = MLRPC_BIND_SIDE_CLIENT; + mbind->context = mcli; + mbind->service = msvc; + mbind->instance_specific = 0; + + *ret_binding_p = mbind; + return (MLRPC_DRC_OK); + +fault_exit: + (*mcli->xa_destruct)(mcli, &mxa); + return (rc); +} + +int +mlrpc_c_call(struct mlrpc_binding *mbind, int opnum, void *params, + mlrpc_heapref_t *heapref) +{ + struct mlrpc_client *mcli = mbind->context; + struct mlrpc_service *msvc = mbind->service; + struct mlrpc_xaction mxa; + mlrpcconn_request_hdr_t *reqhdr; + mlrpcconn_common_header_t *rsphdr; + unsigned long recv_pdu_scan_offset; + int rc; + + if (mlrpc_find_stub_in_svc(msvc, opnum) == NULL) + return (MLRPC_DRC_FAULT_API_OPNUM_INVALID); + + bzero(&mxa, sizeof (mxa)); + mxa.ptype = MLRPC_PTYPE_REQUEST; + mxa.opnum = opnum; + mxa.binding = mbind; + + mlrpc_c_init_hdr(mcli, &mxa); + + reqhdr = &mxa.send_hdr.request_hdr; + reqhdr->common_hdr.ptype = MLRPC_PTYPE_REQUEST; + reqhdr->p_cont_id = mbind->p_cont_id; + reqhdr->opnum = opnum; + + rc = (*mcli->xa_init)(mcli, &mxa, heapref->heap); + if (MLRPC_DRC_IS_FAULT(rc)) + return (rc); + + /* Reserve room for hdr */ + mxa.send_mlnds.pdu_scan_offset = sizeof (*reqhdr); + + rc = mlrpc_encode_call(&mxa, params); + if (!MLRPC_DRC_IS_OK(rc)) + goto fault_exit; + + mxa.send_mlnds.pdu_scan_offset = 0; + + /* + * Now we have the PDU size, we need to set up the + * frag_length and calculate the alloc_hint. + */ + mxa.send_hdr.common_hdr.frag_length = mxa.send_mlnds.pdu_size; + reqhdr->alloc_hint = mxa.send_mlnds.pdu_size - + sizeof (mlrpcconn_request_hdr_t); + + rc = mlrpc_encode_pdu_hdr(&mxa); + if (MLRPC_DRC_IS_FAULT(rc)) + goto fault_exit; + + rc = (*mcli->xa_exchange)(mcli, &mxa); + if (MLRPC_DRC_IS_FAULT(rc)) + goto fault_exit; + + rc = mlrpc_decode_pdu_hdr(&mxa); + if (MLRPC_DRC_IS_FAULT(rc)) + goto fault_exit; + + if (mxa.ptype != MLRPC_PTYPE_RESPONSE) { + rc = MLRPC_DRC_FAULT_RECEIVED_MALFORMED; + goto fault_exit; + } + + rsphdr = &mxa.recv_hdr.common_hdr; + + if (!MLRPC_IS_LAST_FRAG(rsphdr->pfc_flags)) { + /* + * This is a multi-fragment response. + * Preserve the current scan offset while getting + * fragments so that we can continue afterward + * as if we had received the entire response as + * a single PDU. + */ + recv_pdu_scan_offset = mxa.recv_mlnds.pdu_scan_offset; + + if (mlrpc_c_get_frags(mcli, &mxa) < 0) { + rc = MLRPC_DRC_FAULT_RECEIVED_MALFORMED; + goto fault_exit; + } + + mxa.recv_mlnds.pdu_scan_offset = recv_pdu_scan_offset; + } + + rc = mlrpc_decode_return(&mxa, params); + if (MLRPC_DRC_IS_FAULT(rc)) + goto fault_exit; + + rc = (*mcli->xa_preserve)(mcli, &mxa, heapref); + if (MLRPC_DRC_IS_FAULT(rc)) + goto fault_exit; + + (*mcli->xa_destruct)(mcli, &mxa); + return (MLRPC_DRC_OK); + +fault_exit: + (*mcli->xa_destruct)(mcli, &mxa); + return (rc); +} + +int +mlrpc_c_free_heap(struct mlrpc_binding *mbind, mlrpc_heapref_t *heapref) +{ + struct mlrpc_client *mcli = mbind->context; + + (*mcli->xa_release)(mcli, heapref); + return (0); +} + +static void +mlrpc_c_init_hdr(struct mlrpc_client *mcli, struct mlrpc_xaction *mxa) +{ + mlrpcconn_common_header_t *hdr = &mxa->send_hdr.common_hdr; + + hdr->rpc_vers = 5; + hdr->rpc_vers_minor = 0; + hdr->pfc_flags = MLRPC_PFC_FIRST_FRAG + MLRPC_PFC_LAST_FRAG; + hdr->packed_drep.intg_char_rep = MLRPC_REPLAB_CHAR_ASCII; +#ifndef _BIG_ENDIAN + hdr->packed_drep.intg_char_rep |= MLRPC_REPLAB_INTG_LITTLE_ENDIAN; +#endif + /* hdr->frag_length */ + hdr->auth_length = 0; + hdr->call_id = mcli->next_call_id++; +} + +/* + * mlrpc_c_remove_hdr + * + * Remove an RPC fragment header from the received data stream. + * + * Original RPC receive buffer: + * |- frag1 -| |-frag M(partial)-| + * +==================+=============+----+=================+ + * | SmbTransact Rsp1 | SmbTransact | | SmbReadX RspN | + * | (with RPC hdr) | Rsp2 | .. | (with RPC hdr) | + * +-----+------------+-------------+ +-----+-----------+ + * | hdr | data | data | .. | hdr | data | + * +=====+============+=============+----+=====+===========+ + * <------ + * ^ ^ ^ + * | | | + * base_offset hdr data + * + * |-------------------------------------|-----------------| + * offset len + * + * RPC receive buffer (after this call): + * +==================+=============+----+===========+ + * | SmbTransact Rsp1 | SmbTransact | | SmbReadX | + * | (with RPC hdr) | Rsp2 | .. | RspN | + * +-----+------------+-------------+ +-----------+ + * | hdr | data | data | .. | data | + * +=====+============+=============+----+===========+ + */ +static void +mlrpc_c_remove_hdr(struct mlndr_stream *mlnds, int *nbytes) +{ + char *hdr; + char *data; + + hdr = (char *)mlnds->pdu_base_offset + mlnds->pdu_scan_offset; + data = hdr + MLRPC_RSP_HDR_SIZE; + *nbytes -= MLRPC_RSP_HDR_SIZE; + + bcopy(data, hdr, *nbytes); + mlnds->pdu_size -= MLRPC_RSP_HDR_SIZE; +} + +/* + * mlrpc_c_get_frags + * + * A DCE RPC message that is larger than a single fragment is transmitted + * as a series of fragments: 5280 bytes for Windows NT and 4280 bytes for + * both Windows 2000 and 2003. + * + * Collect RPC fragments and append them to the receive stream buffer. + * Each received fragment has a header, which we need to remove as we + * build the full RPC PDU. + * + * The xa_read() calls will translate to SmbReadX requests. Note that + * there is no correspondence between SmbReadX buffering and DCE RPC + * fragment alignment. + * + * Return -1 on error. Otherwise, return the total data count of the + * complete RPC response upon success. + */ +static int +mlrpc_c_get_frags(struct mlrpc_client *mcli, struct mlrpc_xaction *mxa) +{ + struct mlndr_stream *mlnds = &mxa->recv_mlnds; + mlrpcconn_common_header_t hdr; + int frag_rcvd; + int frag_size; + int last_frag; + int nbytes; + + /* + * The scan offest will be used to locate the frag header. + */ + mlnds->pdu_scan_offset = mlnds->pdu_base_offset + mlnds->pdu_size; + + do { + frag_rcvd = 0; + + do { + if ((nbytes = (*mcli->xa_read)(mcli, mxa)) < 0) + return (-1); + + if (frag_rcvd == 0) { + mlrpc_decode_frag_hdr(mlnds, &hdr); + + last_frag = MLRPC_IS_LAST_FRAG(hdr.pfc_flags); + frag_size = hdr.frag_length + - MLRPC_RSP_HDR_SIZE; + + mlrpc_c_remove_hdr(mlnds, &nbytes); + mlnds->pdu_scan_offset += frag_size; + } + + frag_rcvd += nbytes; + + } while (frag_rcvd < frag_size); + } while (!last_frag); + + return (0); +} diff --git a/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_encdec.c b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_encdec.c new file mode 100644 index 0000000000..884683dfe4 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_encdec.c @@ -0,0 +1,349 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <strings.h> +#include <sys/param.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ndr.h> +#include <smbsrv/mlrpc.h> + +#ifdef _BIG_ENDIAN +static const int mlrpc_native_byte_order = MLRPC_REPLAB_INTG_BIG_ENDIAN; +#else +static const int mlrpc_native_byte_order = MLRPC_REPLAB_INTG_LITTLE_ENDIAN; +#endif + +int +mlrpc_encode_decode_common(struct mlrpc_xaction *mxa, int mode, unsigned opnum, + struct ndr_typeinfo *ti, void *datum) +{ + struct mlndr_stream *mlnds; + int m_op = NDR_MODE_TO_M_OP(mode); + int rc; + + if (m_op == NDR_M_OP_MARSHALL) + mlnds = &mxa->send_mlnds; + else + mlnds = &mxa->recv_mlnds; + + /* + * Make sure that mlnds is in the correct mode + */ + if (!NDR_MODE_MATCH(mlnds, mode)) + return (MLRPC_DRC_FAULT_MODE_MISMATCH); + + /* + * Perform the (un)marshalling + */ + if (mlndo_operation(mlnds, ti, opnum, datum)) + return (MLRPC_DRC_OK); + + switch (mlnds->error) { + case NDR_ERR_MALLOC_FAILED: + rc = MLRPC_DRC_FAULT_OUT_OF_MEMORY; + break; + + case NDR_ERR_SWITCH_VALUE_INVALID: + rc = MLRPC_DRC_FAULT_PARAM_0_INVALID; + break; + + case NDR_ERR_UNDERFLOW: + rc = MLRPC_DRC_FAULT_RECEIVED_RUNT; + break; + + case NDR_ERR_GROW_FAILED: + rc = MLRPC_DRC_FAULT_ENCODE_TOO_BIG; + break; + + default: + if (m_op == NDR_M_OP_MARSHALL) + rc = MLRPC_DRC_FAULT_ENCODE_FAILED; + else + rc = MLRPC_DRC_FAULT_DECODE_FAILED; + break; + } + + return (rc); +} + +int +mlrpc_decode_call(struct mlrpc_xaction *mxa, void *params) +{ + int rc; + + rc = mlrpc_encode_decode_common(mxa, NDR_MODE_CALL_RECV, + mxa->opnum, mxa->binding->service->interface_ti, params); + + return (rc + MLRPC_PTYPE_REQUEST); +} + +int +mlrpc_encode_return(struct mlrpc_xaction *mxa, void *params) +{ + int rc; + + rc = mlrpc_encode_decode_common(mxa, NDR_MODE_RETURN_SEND, + mxa->opnum, mxa->binding->service->interface_ti, params); + + return (rc + MLRPC_PTYPE_RESPONSE); +} + +int +mlrpc_encode_call(struct mlrpc_xaction *mxa, void *params) +{ + int rc; + + rc = mlrpc_encode_decode_common(mxa, NDR_MODE_CALL_SEND, + mxa->opnum, mxa->binding->service->interface_ti, params); + + return (rc + MLRPC_PTYPE_REQUEST); +} + +int +mlrpc_decode_return(struct mlrpc_xaction *mxa, void *params) +{ + int rc; + + rc = mlrpc_encode_decode_common(mxa, NDR_MODE_RETURN_RECV, + mxa->opnum, mxa->binding->service->interface_ti, params); + + return (rc + MLRPC_PTYPE_RESPONSE); +} + +int +mlrpc_decode_pdu_hdr(struct mlrpc_xaction *mxa) +{ + mlrpcconn_common_header_t *hdr = &mxa->recv_hdr.common_hdr; + struct mlndr_stream *mlnds = &mxa->recv_mlnds; + int ptype; + int rc; + int charset; + int byte_order; + + if (mlnds->m_op != NDR_M_OP_UNMARSHALL) + return (MLRPC_DRC_FAULT_MODE_MISMATCH + 0xFF); + + /* + * All PDU headers are at least this big + */ + rc = MLNDS_GROW_PDU(mlnds, sizeof (mlrpcconn_common_header_t), 0); + if (!rc) + return (MLRPC_DRC_FAULT_RECEIVED_RUNT + 0xFF); + + /* + * Peek at the first eight bytes to figure out what we're doing. + */ + rc = MLNDS_GET_PDU(mlnds, 0, 8, (char *)hdr, 0, 0); + if (!rc) + return (MLRPC_DRC_FAULT_DECODE_FAILED + 0xFF); + + /* + * Verify the protocol version. + */ + if ((hdr->rpc_vers != 5) || (hdr->rpc_vers_minor != 0)) + return (MLRPC_DRC_FAULT_DECODE_FAILED + 0xFF); + + /* + * Check for ASCII as the character set. This is an ASCII + * versus EBCDIC option and has nothing to do with Unicode. + */ + charset = hdr->packed_drep.intg_char_rep & MLRPC_REPLAB_CHAR_MASK; + if (charset != MLRPC_REPLAB_CHAR_ASCII) + return (MLRPC_DRC_FAULT_DECODE_FAILED + 0xFF); + + /* + * Set the byte swap flag if the PDU byte-order + * is different from the local byte-order. + */ + byte_order = hdr->packed_drep.intg_char_rep & MLRPC_REPLAB_INTG_MASK; + mlnds->swap = (byte_order != mlrpc_native_byte_order) ? 1 : 0; + + ptype = hdr->ptype; + if (ptype == MLRPC_PTYPE_REQUEST && + (hdr->pfc_flags & MLRPC_PFC_OBJECT_UUID) != 0) { + ptype = MLRPC_PTYPE_REQUEST_WITH; /* fake for sizing */ + } + + mxa->ptype = hdr->ptype; + + rc = mlrpc_encode_decode_common(mxa, + NDR_M_OP_AND_DIR_TO_MODE(mlnds->m_op, mlnds->dir), + ptype, &TYPEINFO(mlrpcconn_hdr), hdr); + + return (rc + 0xFF); +} + +/* + * Decode an RPC fragment header. Use mlrpc_decode_pdu_hdr() to process + * the first fragment header then this function to process additional + * fragment headers. + */ +void +mlrpc_decode_frag_hdr(struct mlndr_stream *mlnds, + mlrpcconn_common_header_t *hdr) +{ + mlrpcconn_common_header_t *tmp; + uint8_t *pdu; + int byte_order; + + pdu = (uint8_t *)mlnds->pdu_base_offset + mlnds->pdu_scan_offset; + bcopy(pdu, hdr, MLRPC_RSP_HDR_SIZE); + + /* + * Swap non-byte fields if the PDU byte-order + * is different from the local byte-order. + */ + byte_order = hdr->packed_drep.intg_char_rep & MLRPC_REPLAB_INTG_MASK; + + if (byte_order != mlrpc_native_byte_order) { + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + tmp = (mlrpcconn_common_header_t *)pdu; + + mlnds_bswap(&tmp->frag_length, &hdr->frag_length, + sizeof (WORD)); + mlnds_bswap(&tmp->auth_length, &hdr->auth_length, + sizeof (WORD)); + mlnds_bswap(&tmp->call_id, &hdr->call_id, sizeof (DWORD)); + } +} + +int +mlrpc_encode_pdu_hdr(struct mlrpc_xaction *mxa) +{ + mlrpcconn_common_header_t *hdr = &mxa->send_hdr.common_hdr; + struct mlndr_stream *mlnds = &mxa->send_mlnds; + int ptype; + int rc; + + if (mlnds->m_op != NDR_M_OP_MARSHALL) + return (MLRPC_DRC_FAULT_MODE_MISMATCH + 0xFF); + + ptype = hdr->ptype; + if (ptype == MLRPC_PTYPE_REQUEST && + (hdr->pfc_flags & MLRPC_PFC_OBJECT_UUID) != 0) { + ptype = MLRPC_PTYPE_REQUEST_WITH; /* fake for sizing */ + } + + rc = mlrpc_encode_decode_common(mxa, + NDR_M_OP_AND_DIR_TO_MODE(mlnds->m_op, mlnds->dir), + ptype, &TYPEINFO(mlrpcconn_hdr), hdr); + + return (rc + 0xFF); +} + +/* + * This is a hand-coded derivative of the automatically generated + * (un)marshalling routine for bind_ack headers. bind_ack headers + * have an interior conformant array, which is inconsistent with + * IDL/NDR rules. + */ +extern struct ndr_typeinfo ndt__uchar; +extern struct ndr_typeinfo ndt__ushort; +extern struct ndr_typeinfo ndt__ulong; + +int mlndr__mlrpcconn_bind_ack_hdr(struct ndr_reference *encl_ref); +struct ndr_typeinfo ndt__mlrpcconn_bind_ack_hdr = { + 1, /* NDR version */ + 3, /* alignment */ + NDR_F_STRUCT, /* flags */ + mlndr__mlrpcconn_bind_ack_hdr, /* ndr_func */ + 68, /* pdu_size_fixed_part */ + 0, /* pdu_size_variable_part */ + 68, /* c_size_fixed_part */ + 0, /* c_size_variable_part */ +}; + +/* + * [_no_reorder] + */ +int +mlndr__mlrpcconn_bind_ack_hdr(struct ndr_reference *encl_ref) +{ + struct mlndr_stream *mlnds = encl_ref->stream; + struct mlrpcconn_bind_ack_hdr *val = /*LINTED E_BAD_PTR_CAST_ALIGN*/ + (struct mlrpcconn_bind_ack_hdr *)encl_ref->datum; + struct ndr_reference myref; + unsigned long offset; + + bzero(&myref, sizeof (myref)); + myref.enclosing = encl_ref; + myref.stream = encl_ref->stream; + myref.packed_alignment = 0; + + /* do all members in order */ + NDR_MEMBER(_mlrpcconn_common_header, common_hdr, 0UL); + NDR_MEMBER(_ushort, max_xmit_frag, 16UL); + NDR_MEMBER(_ushort, max_recv_frag, 18UL); + NDR_MEMBER(_ulong, assoc_group_id, 20UL); + + /* port any is the conformant culprit */ + offset = 24UL; + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + val->sec_addr.length = + strlen((char *)val->sec_addr.port_spec) + 1; + break; + + case NDR_M_OP_UNMARSHALL: + break; + + default: + NDR_SET_ERROR(encl_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + NDR_MEMBER(_ushort, sec_addr.length, offset); + NDR_MEMBER_ARR_WITH_DIMENSION(_uchar, sec_addr.port_spec, + offset+2UL, val->sec_addr.length); + + offset += 2; + offset += val->sec_addr.length; + offset += (4 - offset) & 3; + + NDR_MEMBER(_mlrpc_p_result_list, p_result_list, offset); + return (1); +} + +unsigned +mlrpc_bind_ack_hdr_size(struct mlrpcconn_bind_ack_hdr *bahdr) +{ + unsigned offset; + unsigned length; + + /* port any is the conformant culprit */ + offset = 24UL; + + length = strlen((char *)bahdr->sec_addr.port_spec) + 1; + + offset += 2; + offset += length; + offset += (4 - offset) & 3; + offset += sizeof (bahdr->p_result_list); + return (offset); +} diff --git a/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_heap.c b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_heap.c new file mode 100644 index 0000000000..97a16ea010 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_heap.c @@ -0,0 +1,236 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * MLRPC heap management. The heap is used for temporary storage by + * both the client and server side library routines. In order to + * support the different requirements of the various RPCs, the heap + * can grow dynamically if required. We start with a single block + * and perform sub-allocations from it. If an RPC requires more space + * we will continue to add it a block at a time. This means that we + * don't hog lots of memory on every call to support the few times + * that we actually need a lot heap space. + * + * Note that there is no individual free function. Once space has been + * allocated, it remains allocated until the heap is destroyed. This + * shouldn't be an issue because the heap is being filled with data to + * be marshalled or unmarshalled and we need it all to be there until + * the point that the entire heap is no longer required. + */ + +#include <sys/errno.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/uio.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/mlrpc.h> + +/* + * Allocate a heap structure and the first heap block. For many RPC + * operations this will be the only time we need to malloc memory + * in this instance of the heap. The only point of note here is that + * we put the heap management data in the first block to avoid a + * second malloc. Make sure that sizeof(mlrpc_heap_t) is smaller + * than MLRPC_HEAP_BLKSZ. + * + * Note that the heap management data is at the start of the first block. + * + * Returns a pointer to the newly created heap, which is used like an + * opaque handle with the rest of the heap management interface.. + */ +mlrpc_heap_t * +mlrpc_heap_create(void) +{ + mlrpc_heap_t *heap; + char *base; + + if ((base = (char *)malloc(MLRPC_HEAP_BLKSZ)) == NULL) + return (NULL); + + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + heap = (mlrpc_heap_t *)base; + bzero(heap, sizeof (mlrpc_heap_t)); + + heap->iovcnt = MLRPC_HEAP_MAXIOV; + heap->iov = heap->iovec; + heap->iov->iov_base = base; + heap->iov->iov_len = sizeof (mlrpc_heap_t); + heap->top = base + MLRPC_HEAP_BLKSZ; + heap->next = base + sizeof (mlrpc_heap_t); + + return (heap); +} + +/* + * Deallocate all of the memory associated with a heap. This is the + * only way to deallocate heap memory, it isn't possible to free the + * space obtained by individual malloc calls. + * + * Note that the first block contains the heap management data, which + * is deleted last. + */ +void +mlrpc_heap_destroy(mlrpc_heap_t *heap) +{ + int i; + char *p; + + if (heap) { + for (i = 1; i < MLRPC_HEAP_MAXIOV; ++i) { + if ((p = heap->iovec[i].iov_base) != NULL) + free(p); + } + + free(heap); + } +} + +/* + * Allocate space in the specified heap. All requests are padded, if + * required, to ensure dword alignment. If the current iov will be + * exceeded, we allocate a new block and setup the next iov. Otherwise + * all we have to do is move the next pointer and update the current + * iov length. + * + * On success, a pointer to the allocated (dword aligned) area is + * returned. Otherwise a null pointer is returned. + */ +void * +mlrpc_heap_malloc(mlrpc_heap_t *heap, unsigned size) +{ + char *p; + int align; + int incr_size; + + align = (4 - size) & 3; + size += align; + + if (heap == NULL || size == 0) + return (NULL); + + p = heap->next; + + if (p + size > heap->top) { + if ((heap->iovcnt == 0) || ((--heap->iovcnt) == 0)) + return (NULL); + + incr_size = (size < MLRPC_HEAP_BLKSZ) ? MLRPC_HEAP_BLKSZ : size; + + if ((p = (char *)malloc(incr_size)) == NULL) + return (NULL); + + ++heap->iov; + heap->iov->iov_base = p; + heap->iov->iov_len = 0; + heap->top = p + incr_size; + } + + heap->next = p + size; + heap->iov->iov_len += size; + return ((void *)p); +} + +/* + * Convenience function to do heap strdup. + */ +void * +mlrpc_heap_strsave(mlrpc_heap_t *heap, char *s) +{ + int len; + void *p; + + if (s == NULL) + return (NULL); + + /* + * We don't need to clutter the heap with empty strings. + */ + if ((len = strlen(s)) == 0) + return (""); + + if ((p = mlrpc_heap_malloc(heap, len+1)) != NULL) + (void) strcpy((char *)p, s); + + return (p); +} + +/* + * Our regular string marshalling always creates null terminated strings + * but some Windows clients and servers are pedantic about the string + * formats they will accept and require non-null terminated strings. + * This function can be used to build a wide-char, non-null terminated + * string in the heap as a varying/conformant array. We need to do the + * wide-char conversion here because the marshalling code won't be + * aware that this is really a string. + */ +void +mlrpc_heap_mkvcs(mlrpc_heap_t *heap, char *s, mlrpc_vcbuf_t *vcs) +{ + int mlen; + + vcs->wclen = mts_wcequiv_strlen(s); + vcs->wcsize = vcs->wclen; + + mlen = sizeof (struct mlrpc_vcb) + vcs->wcsize + sizeof (mts_wchar_t); + + vcs->vcb = (struct mlrpc_vcb *)mlrpc_heap_malloc(heap, mlen); + + if (vcs->vcb) { + vcs->vcb->vc_first_is = 0; + vcs->vcb->vc_length_is = vcs->wclen / sizeof (mts_wchar_t); + (void) mts_mbstowcs((mts_wchar_t *)vcs->vcb->buffer, s, + vcs->vcb->vc_length_is); + } +} + +int +mlrpc_heap_used(mlrpc_heap_t *heap) +{ + int used = 0; + int i; + + for (i = 0; i < MLRPC_HEAP_MAXIOV; ++i) + used += heap->iovec[i].iov_len; + + return (used); +} + +int +mlrpc_heap_avail(mlrpc_heap_t *heap) +{ + int avail; + int count; + + count = (heap->iovcnt == 0) ? 0 : (heap->iovcnt - 1); + + avail = count * MLRPC_HEAP_BLKSZ; + avail += (heap->top - heap->next); + + return (avail); +} diff --git a/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_server.c b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_server.c new file mode 100644 index 0000000000..a5bf41a617 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_server.c @@ -0,0 +1,836 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Server side RPC handler. + */ + +#include <thread.h> +#include <synch.h> +#include <stdlib.h> +#include <strings.h> +#include <string.h> +#include <time.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/mlsvc.h> +#include <smbsrv/ndr.h> +#include <smbsrv/mlrpc.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ntsid.h> +#include <smbsrv/smb_winpipe.h> + +/* + * Fragment size (5680: NT style). + */ +#define MLRPC_FRAG_SZ 5680 +static unsigned long mlrpc_frag_size = MLRPC_FRAG_SZ; + +/* + * Context table. + */ +#define CTXT_TABLE_ENTRIES 128 +static struct mlsvc_rpc_context context_table[CTXT_TABLE_ENTRIES]; +static mutex_t mlsvc_context_lock; + +static int mlrpc_s_process(struct mlrpc_xaction *); +static int mlrpc_s_bind(struct mlrpc_xaction *); +static int mlrpc_s_request(struct mlrpc_xaction *); +static int mlrpc_generic_call_stub(struct mlrpc_xaction *); +static void mlrpc_reply_prepare_hdr(struct mlrpc_xaction *); +static int mlrpc_s_alter_context(struct mlrpc_xaction *); +static void mlrpc_reply_bind_ack(struct mlrpc_xaction *); +static void mlrpc_reply_fault(struct mlrpc_xaction *, unsigned long); +static int mlrpc_build_reply(struct mlrpc_xaction *); + +/* + * The is the RPC service server-side entry point. All MSRPC encoded + * messages should be passed through here. We use the same context + * structure as the client side but we don't need to set up the client + * side info. + */ +int +mlsvc_rpc_process(smb_pipe_t *inpipe, smb_pipe_t **outpipe, + smb_dr_user_ctx_t *user_ctx) +{ + struct mlsvc_rpc_context *context; + struct mlrpc_xaction *mxa; + struct mlndr_stream *recv_mlnds; + struct mlndr_stream *send_mlnds; + unsigned char *pdu_base_addr; + int datalen; + + if (inpipe == NULL || user_ctx == NULL) + return (-1); + + context = mlsvc_lookup_context(inpipe->sp_pipeid); + if (context == NULL) + return (-1); + + context->user_ctx = user_ctx; + + mxa = (struct mlrpc_xaction *)malloc(sizeof (struct mlrpc_xaction)); + if (mxa == NULL) + return (-1); + + bzero(mxa, sizeof (struct mlrpc_xaction)); + mxa->context = context; + mxa->binding_list = context->binding; + + if ((mxa->heap = mlrpc_heap_create()) == NULL) { + free(mxa); + return (-1); + } + + recv_mlnds = &mxa->recv_mlnds; + + (void) mlnds_initialize(recv_mlnds, inpipe->sp_datalen, + NDR_MODE_CALL_RECV, mxa->heap); + + bcopy(inpipe->sp_data, recv_mlnds->pdu_base_addr, inpipe->sp_datalen); + + send_mlnds = &mxa->send_mlnds; + (void) mlnds_initialize(send_mlnds, 0, + NDR_MODE_RETURN_SEND, mxa->heap); + + (void) mlrpc_s_process(mxa); + + /* + * copy into outpipe + */ + datalen = send_mlnds->pdu_size_with_rpc_hdrs; + *outpipe = calloc(1, sizeof (smb_pipe_t) + datalen); + (*outpipe)->sp_datalen = datalen; + + /* + * Different pointers for single frag vs multi frag responses. + */ + if (send_mlnds->pdu_base_addr_with_rpc_hdrs) + pdu_base_addr = send_mlnds->pdu_base_addr_with_rpc_hdrs; + else + pdu_base_addr = send_mlnds->pdu_base_addr; + + bcopy((char *)pdu_base_addr, (*outpipe)->sp_data, datalen); + mlnds_destruct(&mxa->recv_mlnds); + mlnds_destruct(&mxa->send_mlnds); + mlrpc_heap_destroy(mxa->heap); + free(mxa); + return (datalen); +} + +/* + * Lookup the context for pipeid. If one exists, return a pointer to it. + * Otherwise attempt to allocate a new context and return it. If the + * context table is full, return a null pointer. + */ +struct mlsvc_rpc_context * +mlsvc_lookup_context(int fid) +{ + struct mlsvc_rpc_context *context; + struct mlsvc_rpc_context *available = NULL; + int i; + + (void) mutex_lock(&mlsvc_context_lock); + + for (i = 0; i < CTXT_TABLE_ENTRIES; ++i) { + context = &context_table[i]; + + if (available == NULL && context->fid == 0) { + available = context; + continue; + } + + if (context->fid == fid) { + (void) mutex_unlock(&mlsvc_context_lock); + return (context); + } + } + + if (available) { + bzero(available, sizeof (struct mlsvc_rpc_context)); + available->fid = fid; + + mlrpc_binding_pool_initialize(&available->binding, + available->binding_pool, CTXT_N_BINDING_POOL); + } + + (void) mutex_unlock(&mlsvc_context_lock); + return (available); +} + +/* + * This function should be called to release the context associated + * with a fid when the client performs a close file. + */ +void +mlsvc_rpc_release(int fid) +{ + struct mlsvc_rpc_context *context; + int i; + + (void) mutex_lock(&mlsvc_context_lock); + + for (i = 0; i < CTXT_TABLE_ENTRIES; ++i) { + context = &context_table[i]; + + if (context->fid == fid) { + bzero(context, sizeof (struct mlsvc_rpc_context)); + break; + } + } + + (void) mutex_unlock(&mlsvc_context_lock); +} + +/* + * This is the entry point for all server-side RPC processing. + * It is assumed that the PDU has already been received. + */ +static int +mlrpc_s_process(struct mlrpc_xaction *mxa) +{ + int rc; + + rc = mlrpc_decode_pdu_hdr(mxa); + if (!MLRPC_DRC_IS_OK(rc)) + return (-1); + + (void) mlrpc_reply_prepare_hdr(mxa); + + switch (mxa->ptype) { + case MLRPC_PTYPE_BIND: + rc = mlrpc_s_bind(mxa); + break; + + case MLRPC_PTYPE_REQUEST: + rc = mlrpc_s_request(mxa); + break; + + case MLRPC_PTYPE_ALTER_CONTEXT: + rc = mlrpc_s_alter_context(mxa); + break; + + default: + rc = MLRPC_DRC_FAULT_RPCHDR_PTYPE_INVALID; + break; + } + + if (MLRPC_DRC_IS_FAULT(rc)) + mlrpc_reply_fault(mxa, rc); + + (void) mlrpc_build_reply(mxa); + return (rc); +} + +/* + * Multiple p_cont_elem[]s, multiple transfer_syntaxes[] and multiple + * p_results[] not supported. + */ +static int +mlrpc_s_bind(struct mlrpc_xaction *mxa) +{ + mlrpc_p_cont_list_t *cont_list; + mlrpc_p_result_list_t *result_list; + mlrpc_p_result_t *result; + unsigned p_cont_id; + struct mlrpc_binding *mbind; + mlrpc_uuid_t *as_uuid; + mlrpc_uuid_t *ts_uuid; + char as_buf[64]; + char ts_buf[64]; + int as_vers; + int ts_vers; + struct mlndr_stream *send_mlnds; + struct mlrpc_service *msvc; + int rc; + mlrpc_port_any_t *sec_addr; + + /* acquire targets */ + cont_list = &mxa->recv_hdr.bind_hdr.p_context_elem; + result_list = &mxa->send_hdr.bind_ack_hdr.p_result_list; + result = &result_list->p_results[0]; + + /* + * Set up temporary secondary address port. + * We will correct this later (below). + */ + send_mlnds = &mxa->send_mlnds; + sec_addr = &mxa->send_hdr.bind_ack_hdr.sec_addr; + sec_addr->length = 13; + (void) strcpy((char *)sec_addr->port_spec, "\\PIPE\\ntsvcs"); + + result_list->n_results = 1; + result_list->reserved = 0; + result_list->reserved2 = 0; + result->result = MLRPC_PCDR_ACCEPTANCE; + result->reason = 0; + bzero(&result->transfer_syntax, sizeof (result->transfer_syntax)); + + /* sanity check */ + if (cont_list->n_context_elem != 1 || + cont_list->p_cont_elem[0].n_transfer_syn != 1) { + mlndo_trace("mlrpc_s_bind: warning: multiple p_cont_elem"); + } + + p_cont_id = cont_list->p_cont_elem[0].p_cont_id; + + if ((mbind = mlrpc_find_binding(mxa, p_cont_id)) != NULL) { + /* + * Duplicate p_cont_id. + * Send a bind_ack with a better error. + */ + mlndo_trace("mlrpc_s_bind: duplicate binding"); + return (MLRPC_DRC_FAULT_BIND_PCONT_BUSY); + } + + if ((mbind = mlrpc_new_binding(mxa)) == NULL) { + /* + * No free binding slot + */ + result->result = MLRPC_PCDR_PROVIDER_REJECTION; + result->reason = MLRPC_PPR_LOCAL_LIMIT_EXCEEDED; + mlndo_trace("mlrpc_s_bind: no resources"); + return (MLRPC_DRC_OK); + } + + as_uuid = &cont_list->p_cont_elem[0].abstract_syntax.if_uuid; + as_vers = cont_list->p_cont_elem[0].abstract_syntax.if_version; + + ts_uuid = &cont_list->p_cont_elem[0].transfer_syntaxes[0].if_uuid; + ts_vers = cont_list->p_cont_elem[0].transfer_syntaxes[0].if_version; + + msvc = mlrpc_find_service_by_uuids(as_uuid, as_vers, ts_uuid, ts_vers); + if (!msvc) { + mlrpc_uuid_to_str(as_uuid, as_buf); + mlrpc_uuid_to_str(ts_uuid, ts_buf); + + mlndo_printf(send_mlnds, 0, "mlrpc_s_bind: unknown service"); + mlndo_printf(send_mlnds, 0, "abs=%s v%d, xfer=%s v%d", + as_buf, as_vers, ts_buf, ts_vers); + + result->result = MLRPC_PCDR_PROVIDER_REJECTION; + result->reason = MLRPC_PPR_ABSTRACT_SYNTAX_NOT_SUPPORTED; + return (MLRPC_DRC_OK); + } + + /* + * We can now use the correct secondary address port. + */ + sec_addr = &mxa->send_hdr.bind_ack_hdr.sec_addr; + sec_addr->length = strlen(msvc->sec_addr_port) + 1; + (void) strlcpy((char *)sec_addr->port_spec, msvc->sec_addr_port, + MLRPC_PORT_ANY_MAX_PORT_SPEC); + + mbind->p_cont_id = p_cont_id; + mbind->which_side = MLRPC_BIND_SIDE_SERVER; + /* mbind->context set by app */ + mbind->service = msvc; + mbind->instance_specific = 0; + + mxa->binding = mbind; + + if (msvc->bind_req) { + /* + * Call the service-specific bind() handler. If + * this fails, we shouild send a specific error + * on the bind ack. + */ + rc = (msvc->bind_req)(mxa); + if (MLRPC_DRC_IS_FAULT(rc)) { + mbind->service = 0; /* free binding slot */ + mbind->which_side = 0; + mbind->p_cont_id = 0; + mbind->instance_specific = 0; + return (rc); + } + } + + result->transfer_syntax = + cont_list->p_cont_elem[0].transfer_syntaxes[0]; + + /* + * Special rejection of Windows 2000 DSSETUP interface. + * This interface was introduced in Windows 2000 but has + * been subsequently deprecated due to problems. + */ + if (strcmp(msvc->name, "DSSETUP") == 0) { + result->result = MLRPC_PCDR_PROVIDER_REJECTION; + result->reason = MLRPC_PPR_ABSTRACT_SYNTAX_NOT_SUPPORTED; + } + + return (MLRPC_DRC_BINDING_MADE); +} + +/* + * mlrpc_s_alter_context + * + * The alter context request is used to request additional presentation + * context for another interface and/or version. It's very similar to a + * bind request. + * + * We don't fully support multiple contexts so, for now, we reject this + * request. Windows 2000 clients attempt to use an alternate LSA context + * when ACLs are modified. + */ +static int +mlrpc_s_alter_context(struct mlrpc_xaction *mxa) +{ + mlrpc_p_result_list_t *result_list; + mlrpc_p_result_t *result; + mlrpc_p_cont_list_t *cont_list; + struct mlrpc_binding *mbind; + struct mlrpc_service *msvc; + unsigned p_cont_id; + mlrpc_uuid_t *as_uuid; + mlrpc_uuid_t *ts_uuid; + int as_vers; + int ts_vers; + mlrpc_port_any_t *sec_addr; + + result_list = &mxa->send_hdr.bind_ack_hdr.p_result_list; + result_list->n_results = 1; + result_list->reserved = 0; + result_list->reserved2 = 0; + + result = &result_list->p_results[0]; + result->result = MLRPC_PCDR_ACCEPTANCE; + result->reason = 0; + bzero(&result->transfer_syntax, sizeof (result->transfer_syntax)); + + if (mxa != NULL) { + result->result = MLRPC_PCDR_PROVIDER_REJECTION; + result->reason = MLRPC_PPR_ABSTRACT_SYNTAX_NOT_SUPPORTED; + return (MLRPC_DRC_OK); + } + + cont_list = &mxa->recv_hdr.bind_hdr.p_context_elem; + p_cont_id = cont_list->p_cont_elem[0].p_cont_id; + + if ((mbind = mlrpc_find_binding(mxa, p_cont_id)) != NULL) + return (MLRPC_DRC_FAULT_BIND_PCONT_BUSY); + + if ((mbind = mlrpc_new_binding(mxa)) == NULL) { + result->result = MLRPC_PCDR_PROVIDER_REJECTION; + result->reason = MLRPC_PPR_LOCAL_LIMIT_EXCEEDED; + return (MLRPC_DRC_OK); + } + + as_uuid = &cont_list->p_cont_elem[0].abstract_syntax.if_uuid; + as_vers = cont_list->p_cont_elem[0].abstract_syntax.if_version; + + ts_uuid = &cont_list->p_cont_elem[0].transfer_syntaxes[0].if_uuid; + ts_vers = cont_list->p_cont_elem[0].transfer_syntaxes[0].if_version; + + msvc = mlrpc_find_service_by_uuids(as_uuid, as_vers, ts_uuid, ts_vers); + if (msvc == 0) { + result->result = MLRPC_PCDR_PROVIDER_REJECTION; + result->reason = MLRPC_PPR_ABSTRACT_SYNTAX_NOT_SUPPORTED; + return (MLRPC_DRC_OK); + } + + mbind->p_cont_id = p_cont_id; + mbind->which_side = MLRPC_BIND_SIDE_SERVER; + /* mbind->context set by app */ + mbind->service = msvc; + mbind->instance_specific = 0; + mxa->binding = mbind; + + sec_addr = &mxa->send_hdr.bind_ack_hdr.sec_addr; + sec_addr->length = 0; + bzero(sec_addr->port_spec, MLRPC_PORT_ANY_MAX_PORT_SPEC); + + result->transfer_syntax = + cont_list->p_cont_elem[0].transfer_syntaxes[0]; + + return (MLRPC_DRC_BINDING_MADE); +} + +static int +mlrpc_s_request(struct mlrpc_xaction *mxa) +{ + struct mlrpc_binding *mbind; + struct mlrpc_service *msvc; + unsigned p_cont_id; + int rc; + + mxa->opnum = mxa->recv_hdr.request_hdr.opnum; + p_cont_id = mxa->recv_hdr.request_hdr.p_cont_id; + + if ((mbind = mlrpc_find_binding(mxa, p_cont_id)) == NULL) + return (MLRPC_DRC_FAULT_REQUEST_PCONT_INVALID); + + mxa->binding = mbind; + msvc = mbind->service; + + /* + * Make room for the response hdr. + */ + mxa->send_mlnds.pdu_scan_offset = MLRPC_RSP_HDR_SIZE; + + if (msvc->call_stub) + rc = (*msvc->call_stub)(mxa); + else + rc = mlrpc_generic_call_stub(mxa); + + if (MLRPC_DRC_IS_FAULT(rc)) { + mlndo_printf(0, 0, "%s[0x%02x]: 0x%04x", + msvc->name, mxa->opnum, rc); + } + + return (rc); +} + +/* + * The transaction and the two mlnds streams use the same heap, which + * should already exist at this point. The heap will also be available + * to the stub. + */ +static int +mlrpc_generic_call_stub(struct mlrpc_xaction *mxa) +{ + struct mlrpc_binding *mbind = mxa->binding; + struct mlrpc_service *msvc = mbind->service; + struct ndr_typeinfo *intf_ti = msvc->interface_ti; + struct mlrpc_stub_table *ste; + int opnum = mxa->opnum; + unsigned p_len = intf_ti->c_size_fixed_part; + char *param; + int rc; + + if (mxa->heap == NULL) { + mlndo_printf(0, 0, "%s[0x%02x]: no heap", msvc->name, opnum); + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + } + + if ((ste = mlrpc_find_stub_in_svc(msvc, opnum)) == NULL) { + mlndo_printf(0, 0, "%s[0x%02x]: invalid opnum", + msvc->name, opnum); + return (MLRPC_DRC_FAULT_REQUEST_OPNUM_INVALID); + } + + if ((param = mlrpc_heap_malloc(mxa->heap, p_len)) == NULL) + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + + bzero(param, p_len); + + rc = mlrpc_decode_call(mxa, param); + if (!MLRPC_DRC_IS_OK(rc)) + return (rc); + + rc = (*ste->func)(param, mxa); + if (rc == MLRPC_DRC_OK) + rc = mlrpc_encode_return(mxa, param); + + return (rc); +} + +/* + * We can perform some initial setup of the response header here. + * We also need to cache some of the information from the bind + * negotiation for use during subsequent RPC calls. + */ +static void +mlrpc_reply_prepare_hdr(struct mlrpc_xaction *mxa) +{ + mlrpcconn_common_header_t *rhdr = &mxa->recv_hdr.common_hdr; + mlrpcconn_common_header_t *hdr = &mxa->send_hdr.common_hdr; + + hdr->rpc_vers = 5; + hdr->rpc_vers_minor = 0; + hdr->pfc_flags = MLRPC_PFC_FIRST_FRAG + MLRPC_PFC_LAST_FRAG; + hdr->packed_drep = rhdr->packed_drep; + hdr->frag_length = 0; + hdr->auth_length = 0; + hdr->call_id = rhdr->call_id; +#ifdef _BIG_ENDIAN + hdr->packed_drep.intg_char_rep = MLRPC_REPLAB_CHAR_ASCII + | MLRPC_REPLAB_INTG_BIG_ENDIAN; +#else + hdr->packed_drep.intg_char_rep = MLRPC_REPLAB_CHAR_ASCII + | MLRPC_REPLAB_INTG_LITTLE_ENDIAN; +#endif + + switch (mxa->ptype) { + case MLRPC_PTYPE_BIND: + hdr->ptype = MLRPC_PTYPE_BIND_ACK; + mxa->send_hdr.bind_ack_hdr.max_xmit_frag = + mxa->recv_hdr.bind_hdr.max_xmit_frag; + mxa->send_hdr.bind_ack_hdr.max_recv_frag = + mxa->recv_hdr.bind_hdr.max_recv_frag; + mxa->send_hdr.bind_ack_hdr.assoc_group_id = + mxa->recv_hdr.bind_hdr.assoc_group_id; + + if (mxa->send_hdr.bind_ack_hdr.assoc_group_id == 0) + mxa->send_hdr.bind_ack_hdr.assoc_group_id = time(0); + + /* + * Save the maximum fragment sizes + * for use with subsequent requests. + */ + mxa->context->max_xmit_frag = + mxa->recv_hdr.bind_hdr.max_xmit_frag; + + mxa->context->max_recv_frag = + mxa->recv_hdr.bind_hdr.max_recv_frag; + + break; + + case MLRPC_PTYPE_REQUEST: + hdr->ptype = MLRPC_PTYPE_RESPONSE; + /* mxa->send_hdr.response_hdr.alloc_hint */ + mxa->send_hdr.response_hdr.p_cont_id = + mxa->recv_hdr.request_hdr.p_cont_id; + mxa->send_hdr.response_hdr.cancel_count = 0; + mxa->send_hdr.response_hdr.reserved = 0; + break; + + case MLRPC_PTYPE_ALTER_CONTEXT: + hdr->ptype = MLRPC_PTYPE_ALTER_CONTEXT_RESP; + /* + * The max_xmit_frag, max_recv_frag + * and assoc_group_id are ignored. + */ + break; + + default: + hdr->ptype = 0xFF; + } +} + +/* + * Finish and encode the bind acknowledge (MLRPC_PTYPE_BIND_ACK) header. + * The frag_length is different from a regular RPC response. + */ +static void +mlrpc_reply_bind_ack(struct mlrpc_xaction *mxa) +{ + mlrpcconn_common_header_t *hdr; + mlrpcconn_bind_ack_hdr_t *bahdr; + + hdr = &mxa->send_hdr.common_hdr; + bahdr = &mxa->send_hdr.bind_ack_hdr; + hdr->frag_length = mlrpc_bind_ack_hdr_size(bahdr); +} + +/* + * Signal an RPC fault. The stream is reset and we overwrite whatever + * was in the response header with the fault information. + */ +static void +mlrpc_reply_fault(struct mlrpc_xaction *mxa, unsigned long drc) +{ + mlrpcconn_common_header_t *rhdr = &mxa->recv_hdr.common_hdr; + mlrpcconn_common_header_t *hdr = &mxa->send_hdr.common_hdr; + struct mlndr_stream *mlnds = &mxa->send_mlnds; + unsigned long fault_status; + + MLNDS_RESET(mlnds); + + hdr->rpc_vers = 5; + hdr->rpc_vers_minor = 0; + hdr->pfc_flags = MLRPC_PFC_FIRST_FRAG + MLRPC_PFC_LAST_FRAG; + hdr->packed_drep = rhdr->packed_drep; + hdr->frag_length = sizeof (mxa->send_hdr.fault_hdr); + hdr->auth_length = 0; + hdr->call_id = rhdr->call_id; +#ifdef _BIG_ENDIAN + hdr->packed_drep.intg_char_rep = MLRPC_REPLAB_CHAR_ASCII + | MLRPC_REPLAB_INTG_BIG_ENDIAN; +#else + hdr->packed_drep.intg_char_rep = MLRPC_REPLAB_CHAR_ASCII + | MLRPC_REPLAB_INTG_LITTLE_ENDIAN; +#endif + + switch (drc & MLRPC_DRC_MASK_SPECIFIER) { + case MLRPC_DRC_FAULT_OUT_OF_MEMORY: + case MLRPC_DRC_FAULT_ENCODE_TOO_BIG: + fault_status = MLRPC_FAULT_NCA_OUT_ARGS_TOO_BIG; + break; + + case MLRPC_DRC_FAULT_REQUEST_PCONT_INVALID: + fault_status = MLRPC_FAULT_NCA_INVALID_PRES_CONTEXT_ID; + break; + + case MLRPC_DRC_FAULT_REQUEST_OPNUM_INVALID: + fault_status = MLRPC_FAULT_NCA_OP_RNG_ERROR; + break; + + case MLRPC_DRC_FAULT_DECODE_FAILED: + case MLRPC_DRC_FAULT_ENCODE_FAILED: + fault_status = MLRPC_FAULT_NCA_PROTO_ERROR; + break; + + default: + fault_status = MLRPC_FAULT_NCA_UNSPEC_REJECT; + break; + } + + mxa->send_hdr.fault_hdr.common_hdr.ptype = MLRPC_PTYPE_FAULT; + mxa->send_hdr.fault_hdr.status = fault_status; + mxa->send_hdr.response_hdr.alloc_hint = hdr->frag_length; +} + +static int +mlrpc_build_reply(struct mlrpc_xaction *mxa) +{ + mlrpcconn_common_header_t *hdr = &mxa->send_hdr.common_hdr; + struct mlndr_stream *mlnds = &mxa->send_mlnds; + unsigned long pdu_size; + unsigned long frag_size; + unsigned long pdu_data_size; + unsigned long frag_data_size; + uint32_t rem_dlen; + uint32_t save_rem_dlen; + uint32_t bytesoff; + uint32_t cnt; + uint32_t obytes; + uint32_t num_ext_frags; + uint16_t last_frag = 0; + uchar_t *frag_startp; + mlrpcconn_common_header_t *rpc_hdr; + + hdr = &mxa->send_hdr.common_hdr; + + frag_size = mlrpc_frag_size; + pdu_size = mlnds->pdu_size; + + if (pdu_size <= frag_size) { + /* + * Single fragment response. The PDU size may be zero + * here (i.e. bind or fault response). So don't make + * any assumptions about it until after the header is + * encoded. + */ + switch (hdr->ptype) { + case MLRPC_PTYPE_BIND_ACK: + mlrpc_reply_bind_ack(mxa); + break; + + case MLRPC_PTYPE_FAULT: + /* already setup */ + break; + + case MLRPC_PTYPE_RESPONSE: + hdr->frag_length = pdu_size; + mxa->send_hdr.response_hdr.alloc_hint = + hdr->frag_length; + break; + + default: + hdr->frag_length = pdu_size; + break; + } + + mlnds->pdu_scan_offset = 0; + (void) mlrpc_encode_pdu_hdr(mxa); + + mlnds->pdu_size_with_rpc_hdrs = mlnds->pdu_size; + mlnds->pdu_base_addr_with_rpc_hdrs = 0; + return (0); + } + + /* + * Multiple fragment response. + */ + hdr->pfc_flags = MLRPC_PFC_FIRST_FRAG; + hdr->frag_length = frag_size; + mxa->send_hdr.response_hdr.alloc_hint = pdu_size - MLRPC_RSP_HDR_SIZE; + mlnds->pdu_scan_offset = 0; + + (void) mlrpc_encode_pdu_hdr(mxa); + + /* + * We need to update the 24-byte header in subsequent fragments. + * + * pdu_data_size: total data remaining to be handled + * frag_size: total fragment size including header + * frag_data_size: data in fragment + * (i.e. frag_size - MLRPC_RSP_HDR_SIZE) + */ + pdu_data_size = pdu_size - MLRPC_RSP_HDR_SIZE; + frag_data_size = frag_size - MLRPC_RSP_HDR_SIZE; + + num_ext_frags = pdu_data_size / frag_data_size; + /* + * if the outpipe is bigger than a frag_size, we need + * to stretch the pipe and insert an RPC header at each + * frag boundary. This outpipe gets chunked out in xdrlen + * sizes for each trans request + */ + mlnds->pdu_base_addr_with_rpc_hdrs + = malloc(pdu_size + (num_ext_frags * MLRPC_RSP_HDR_SIZE)); + mlnds->pdu_size_with_rpc_hdrs = + mlnds->pdu_size + (num_ext_frags * MLRPC_RSP_HDR_SIZE); + + /* + * Start stretching loop. + */ + bcopy(mlnds->pdu_base_addr, + mlnds->pdu_base_addr_with_rpc_hdrs, frag_size); + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + rpc_hdr = (mlrpcconn_common_header_t *) + mlnds->pdu_base_addr_with_rpc_hdrs; + rpc_hdr->pfc_flags = MLRPC_PFC_FIRST_FRAG; + rem_dlen = pdu_data_size - frag_size; + bytesoff = frag_size; + cnt = 1; + while (num_ext_frags--) { + /* first copy the RPC header to the front of the frag */ + bcopy(mlnds->pdu_base_addr, mlnds->pdu_base_addr_with_rpc_hdrs + + (cnt * frag_size), MLRPC_RSP_HDR_SIZE); + + /* then copy the data portion of the frag */ + save_rem_dlen = rem_dlen; + if (rem_dlen >= (frag_size - MLRPC_RSP_HDR_SIZE)) { + rem_dlen = rem_dlen - frag_size + MLRPC_RSP_HDR_SIZE; + obytes = frag_size - MLRPC_RSP_HDR_SIZE; + } else { + last_frag = 1; /* this is the last one */ + obytes = rem_dlen; + } + + frag_startp = mlnds->pdu_base_addr_with_rpc_hdrs + + (cnt * frag_size); + bcopy(mlnds->pdu_base_addr + bytesoff, + frag_startp + MLRPC_RSP_HDR_SIZE, obytes); + + /* set the FRAG FLAGS in the frag header spot */ + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + rpc_hdr = (mlrpcconn_common_header_t *)frag_startp; + if (last_frag) { + rpc_hdr->frag_length = save_rem_dlen; + rpc_hdr->pfc_flags = MLRPC_PFC_LAST_FRAG; + } else { + rpc_hdr->pfc_flags = 0; + } + + bytesoff += (frag_size - MLRPC_RSP_HDR_SIZE); + cnt++; + } + + return (0); +} diff --git a/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_svc.c b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_svc.c new file mode 100644 index 0000000000..771e54b7e4 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/mlrpc_svc.c @@ -0,0 +1,286 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + + +#include <stdio.h> +#include <string.h> +#include <strings.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ndr.h> +#include <smbsrv/mlrpc.h> + +#define NDL_MAX_SERVICES 32 +static struct mlrpc_service *mlrpc_services[NDL_MAX_SERVICES]; + +struct mlrpc_stub_table * +mlrpc_find_stub_in_svc(struct mlrpc_service *msvc, int opnum) +{ + struct mlrpc_stub_table *ste; + + for (ste = msvc->stub_table; ste->func; ste++) { + if (ste->opnum == opnum) + return (ste); + } + + return (NULL); +} + +struct mlrpc_service * +mlrpc_find_service_by_name(const char *name) +{ + struct mlrpc_service *msvc; + int i; + + for (i = 0; i < NDL_MAX_SERVICES; i++) { + if ((msvc = mlrpc_services[i]) == NULL) + continue; + + if (strcasecmp(name, msvc->name) != 0) + continue; + + mlndo_printf(0, 0, "%s %s", msvc->name, msvc->desc); + return (msvc); + } + + return (NULL); +} + +struct mlrpc_service * +mlrpc_find_service_by_uuids(mlrpc_uuid_t *as_uuid, int as_vers, + mlrpc_uuid_t *ts_uuid, int ts_vers) +{ + struct mlrpc_service *msvc; + char abstract_syntax[128]; + char transfer_syntax[128]; + int i; + + if (as_uuid) + mlrpc_uuid_to_str(as_uuid, abstract_syntax); + + if (ts_uuid) + mlrpc_uuid_to_str(ts_uuid, transfer_syntax); + + for (i = 0; i < NDL_MAX_SERVICES; i++) { + if ((msvc = mlrpc_services[i]) == NULL) + continue; + + if (as_uuid) { + if (msvc->abstract_syntax_uuid == 0) + continue; + + if (msvc->abstract_syntax_version != as_vers) + continue; + + if (strcasecmp(abstract_syntax, + msvc->abstract_syntax_uuid)) + continue; + } + + if (ts_uuid) { + if (msvc->transfer_syntax_uuid == 0) + continue; + + if (msvc->transfer_syntax_version != ts_vers) + continue; + + if (strcasecmp(transfer_syntax, + msvc->transfer_syntax_uuid)) + continue; + } + + mlndo_printf(0, 0, "%s %s", msvc->name, msvc->desc); + return (msvc); + } + + return (NULL); +} + +/* + * Register a service. + * + * Returns: + * 0 Success + * -1 Duplicate service + * -2 Duplicate name + * -3 Table overflow + */ +int +mlrpc_register_service(struct mlrpc_service *msvc) +{ + struct mlrpc_service *p; + int free_slot = -1; + int i; + + for (i = 0; i < NDL_MAX_SERVICES; i++) { + if ((p = mlrpc_services[i]) == NULL) { + if (free_slot < 0) + free_slot = i; + continue; + } + + if (p == msvc) + return (-1); + + if (strcasecmp(p->name, msvc->name) == 0) + return (-2); + } + + if (free_slot < 0) + return (-3); + + mlrpc_services[free_slot] = msvc; + return (0); +} + +void +mlrpc_unregister_service(struct mlrpc_service *msvc) +{ + int i; + + for (i = 0; i < NDL_MAX_SERVICES; i++) { + if (mlrpc_services[i] == msvc) + mlrpc_services[i] = NULL; + } +} + +int +mlrpc_list_services(char *buffer, int bufsize) +{ + struct mlrpc_service *msvc; + smb_ctxbuf_t ctx; + int i; + + (void) smb_ctxbuf_init(&ctx, (uint8_t *)buffer, bufsize); + + for (i = 0; i < NDL_MAX_SERVICES; i++) { + if ((msvc = mlrpc_services[i]) != 0) { + (void) smb_ctxbuf_printf(&ctx, "%-16s %s\n", + msvc->name, msvc->desc); + } + } + + return (smb_ctxbuf_len(&ctx)); +} + +void +mlrpc_uuid_to_str(mlrpc_uuid_t *uuid, char *str) +{ + (void) sprintf(str, "%08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x", + uuid->data1, uuid->data2, uuid->data3, + uuid->data4[0], uuid->data4[1], + uuid->data4[2], uuid->data4[3], + uuid->data4[4], uuid->data4[5], + uuid->data4[6], uuid->data4[7]); +} + +int +mlrpc_str_to_uuid(char *str, mlrpc_uuid_t *uuid) +{ + char *p = str; + char *q; + char buf[4]; + int i; + + uuid->data1 = strtoul(p, &p, 16); + if (*p != '-') + return (0); + p++; + + uuid->data2 = strtol(p, &p, 16); + if (*p != '-') + return (0); + p++; + + uuid->data3 = strtol(p, &p, 16); + if (*p != '-') + return (0); + p++; + + for (i = 0; i < 8; i++) { + if (p[0] == 0 || p[1] == 0) + return (0); + + buf[0] = *p++; + buf[1] = *p++; + buf[2] = 0; + uuid->data4[i] = strtol(buf, &q, 16); + if (*q != 0) + return (0); + } + + if (*p != 0) + return (0); + + return (1); +} + +void +mlrpc_binding_pool_initialize(struct mlrpc_binding **headpp, + struct mlrpc_binding pool[], unsigned n_pool) +{ + struct mlrpc_binding *head = NULL; + int ix; + + for (ix = n_pool - 1; ix >= 0; ix--) { + pool[ix].next = head; + pool[ix].service = NULL; + pool[ix].p_cont_id = 0xffff; + pool[ix].instance_specific = 0; + head = &pool[ix]; + } + + *headpp = head; +} + +struct mlrpc_binding * +mlrpc_find_binding(struct mlrpc_xaction *mxa, mlrpc_p_context_id_t p_cont_id) +{ + struct mlrpc_binding *mbind; + + for (mbind = mxa->binding_list; mbind; mbind = mbind->next) { + if (mbind->service != NULL && + mbind->which_side == MLRPC_BIND_SIDE_SERVER && + mbind->p_cont_id == p_cont_id) + break; + } + + return (mbind); +} + +struct mlrpc_binding * +mlrpc_new_binding(struct mlrpc_xaction *mxa) +{ + struct mlrpc_binding *mbind; + + for (mbind = mxa->binding_list; mbind; mbind = mbind->next) { + if (mbind->service == NULL) + break; + } + + return (mbind); +} diff --git a/usr/src/lib/smbsrv/libmlrpc/i386/Makefile b/usr/src/lib/smbsrv/libmlrpc/i386/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/i386/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libmlrpc/sparc/Makefile b/usr/src/lib/smbsrv/libmlrpc/sparc/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/sparc/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libmlrpc/sparcv9/Makefile b/usr/src/lib/smbsrv/libmlrpc/sparcv9/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/sparcv9/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/smbsrv/libmlsvc/Makefile b/usr/src/lib/smbsrv/libmlsvc/Makefile new file mode 100644 index 0000000000..21f844a2c0 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +HDRS= libmlsvc.h + +include ../Makefile.smbsrv diff --git a/usr/src/lib/smbsrv/libmlsvc/Makefile.com b/usr/src/lib/smbsrv/libmlsvc/Makefile.com new file mode 100644 index 0000000000..e0c1ee5c4c --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/Makefile.com @@ -0,0 +1,86 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#pragma ident "%Z%%M% %I% %E% SMI" +# + +LIBRARY = libmlsvc.a +VERS = .1 + +OBJS_COMMON = \ + lsalib.o \ + lsar_lookup.o \ + lsar_open.o \ + mlsvc_client.o \ + mlsvc_dssetup.o \ + mlsvc_handle.o \ + mlsvc_init.o \ + mlsvc_logr.o \ + mlsvc_lsa.o \ + mlsvc_netr.o \ + mlsvc_sam.o \ + mlsvc_srvsvc.o \ + mlsvc_svcctl.o \ + mlsvc_util.o \ + mlsvc_winreg.o \ + mlsvc_wkssvc.o \ + netdfs.o \ + netr_auth.o \ + netr_logon.o \ + samlib.o \ + samr_open.o \ + samr_lookup.o \ + secdb.o \ + srvsvc_client.o \ + lmshare.o \ + smb_share_util.o \ + smb_autohome.o + +# Automatically generated from .ndl files +NDLLIST = \ + dssetup \ + eventlog \ + lsarpc \ + netdfs \ + netlogon \ + samrpc \ + spoolss \ + srvsvc \ + svcctl \ + winreg + +OBJECTS= $(OBJS_COMMON) $(NDLLIST:%=%_ndr.o) + +include ../../../Makefile.lib +include ../../Makefile.lib + +INCS += -I$(SRC)/common/smbsrv + +LDLIBS += -lmlrpc -lsmbrdr -lsmb -lsmbns -lshare -lnsl -lc + +SRCS= $(OBJS_COMMON:%.o=$(SRCDIR)/%.c) \ + $(OBJS_SHARED:%.o=$(SRC)/common/smbsrv/%.c) + +include ../../Makefile.targ +include ../../../Makefile.targ diff --git a/usr/src/lib/smbsrv/libmlsvc/amd64/Makefile b/usr/src/lib/smbsrv/libmlsvc/amd64/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/amd64/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h b/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h new file mode 100644 index 0000000000..71cc37af48 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h @@ -0,0 +1,237 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBMLSVC_H +#define _LIBMLSVC_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <smbsrv/ntsid.h> +#include <smbsrv/hash_table.h> +#include <smbsrv/smb_token.h> +#include <smbsrv/smb_privilege.h> +#include <smbsrv/lmshare.h> +#include <smbsrv/libsmb.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern int mlsvc_init(void); +extern int mlsvc_is_local_domain(const char *); +extern DWORD lsa_query_primary_domain_info(void); +extern DWORD lsa_query_account_domain_info(void); +extern DWORD lsa_enum_trusted_domains(void); + +extern boolean_t locate_resource_pdc(char *); + +#define SMB_AUTOHOME_FILE "smbautohome" +#define SMB_AUTOHOME_PATH "/etc" + +typedef struct smb_autohome { + struct smb_autohome *ah_next; + uint32_t ah_hits; + time_t ah_timestamp; + char *ah_name; /* User account name */ + char *ah_path; /* Home directory path */ + char *ah_container; /* ADS container distinguished name */ +} smb_autohome_t; + +extern int smb_autohome_add(const char *); +extern int smb_autohome_remove(const char *); +extern int smb_is_autohome(const lmshare_info_t *); +extern void smb_autohome_setent(void); +extern void smb_autohome_endent(void); +extern smb_autohome_t *smb_autohome_getent(const char *name); +extern smb_autohome_t *smb_autohome_lookup(const char *name); + +/* + * Local groups + */ +#define NT_GROUP_FMRI_PREFIX "network/smb/group" + +typedef enum { + RWLOCK_NONE, + RWLOCK_WRITER, + RWLOCK_READER +} krwmode_t; + +typedef struct nt_group_data { + void *data; + int size; +} nt_group_data_t; + +/* + * IMPORTANT NOTE: + * If you change nt_group_member_t, nt_group_members_t, or nt_group_t + * structures, you MIGHT have to change following functions accordingly: + * nt_group_setfields + * nt_group_init_size + * nt_group_init + */ +typedef struct nt_group_member { + uint16_t info_size; /* size of the whole structure */ + uint16_t sid_name_use; /* type of the specified SID */ + char *account; /* Pointer to account name of member */ + nt_sid_t sid; /* Variable length */ +} nt_group_member_t; + +typedef struct nt_group_members { + uint32_t size; /* in bytes */ + uint32_t count; + nt_group_member_t list[ANY_SIZE_ARRAY]; +} nt_group_members_t; + +typedef struct nt_group { + time_t age; + nt_group_data_t info; + /* + * following fields point to a contigous block + * of memory that is read and written from/to DB + */ + uint32_t *attr; + uint16_t *sid_name_use; + char *name; + char *comment; + nt_sid_t *sid; + smb_privset_t *privileges; + nt_group_members_t *members; +} nt_group_t; + +typedef struct nt_group_iterator { + HT_ITERATOR *iterator; + int iteration; +} nt_group_iterator_t; + +extern int nt_group_num_groups(void); +extern uint32_t nt_group_add(char *, char *); +extern uint32_t nt_group_modify(char *, char *, char *); +extern uint32_t nt_group_delete(char *); +extern nt_group_t *nt_group_getinfo(char *, krwmode_t); +extern void nt_group_putinfo(nt_group_t *); + +extern int nt_group_getpriv(nt_group_t *, uint32_t); +extern uint32_t nt_group_setpriv(nt_group_t *, uint32_t, uint32_t); + +/* Member manipulation functions */ +extern int nt_group_is_member(nt_group_t *, nt_sid_t *); +extern uint32_t nt_group_del_member(nt_group_t *, void *, int); +extern uint32_t nt_group_add_member(nt_group_t *, nt_sid_t *, uint16_t, char *); +extern int nt_group_num_members(nt_group_t *); + +extern void nt_group_ht_lock(krwmode_t); +extern void nt_group_ht_unlock(void); + +extern nt_group_iterator_t *nt_group_open_iterator(void); +extern void nt_group_close_iterator(nt_group_iterator_t *); +extern nt_group_t *nt_group_iterate(nt_group_iterator_t *); + +extern int nt_group_cache_size(void); + +extern int nt_group_member_list(int offset, nt_group_t *grp, + ntgrp_member_list_t *rmembers); +extern void nt_group_list(int offset, char *pattern, ntgrp_list_t *list); + +extern uint32_t sam_init(void); + +extern uint32_t nt_group_add_member_byname(char *, char *); +extern uint32_t nt_group_del_member_byname(nt_group_t *, char *); +extern void nt_group_add_groupprivs(nt_group_t *, smb_privset_t *); + +extern uint32_t nt_groups_member_privs(nt_sid_t *, smb_privset_t *); +extern int nt_groups_member_ngroups(nt_sid_t *); +extern uint32_t nt_groups_member_groups(nt_sid_t *, smb_id_t *, int); +extern nt_group_t *nt_groups_lookup_rid(uint32_t); +extern int nt_groups_count(int); + +/* + * source for account name size is MSDN + */ +#define NT_GROUP_NAME_CHAR_MAX 32 +#define NT_GROUP_NAME_MAX (NT_GROUP_NAME_CHAR_MAX * 3 + 1) +#define NT_GROUP_USER_NAME_MAX (NT_GROUP_NAME_CHAR_MAX * 3 + 1) +#define NT_GROUP_MEMBER_NAME_MAX (NT_GROUP_NAME_CHAR_MAX * 3 + 1) +#define NT_GROUP_COMMENT_MAX 256 + +/* + * flags for count operation + */ +#define NT_GROUP_CNT_BUILTIN 1 +#define NT_GROUP_CNT_LOCAL 2 +#define NT_GROUP_CNT_ALL 3 + +/* + * flag to distinguish between add and modify + * operations. + */ +#define NT_GROUP_OP_CHANGE 1 +#define NT_GROUP_OP_SYNC 2 + +/* + * specify key type for deleting a member i.e. + * whether it's member's name or member's SID. + */ +#define NT_GROUP_KEY_SID 1 +#define NT_GROUP_KEY_NAME 2 + +/* Macro for walking members */ +#define NEXT_MEMBER(m) (nt_group_member_t *)((char *)(m) + (m)->info_size) + +/* + * When NT requests the security descriptor for a local file that + * doesn't already have a one, we generate one on-the-fly. The SD + * contains both user and group SIDs. The problem is that we need a + * way to distinguish a user SID from a group SID when NT performs a + * subsequent SID lookup to obtain the appropriate name to display. + * The following macros are used to map to and from an external + * representation so that we can tell the difference between UIDs + * and GIDs. The local UID/GID is shifted left and the LSB is used + * to distinguish the id type before it is inserted into the SID. + * We can then use this type identifier during lookup operations. + */ +#define SAM_MIN_RID 1000 +#define SAM_RT_ERROR -1 +#define SAM_RT_UNIX_UID 0 +#define SAM_RT_UNIX_GID 1 +#define SAM_RT_NT_UID 2 +#define SAM_RT_NT_GID 3 +#define SAM_RT_MASK 0x3 +#define SAM_RT_EVERYONE 4 +#define SAM_RT_UNKNOWN 5 + +#define SAM_RID_TYPE(rid) ((rid) & SAM_RT_MASK) +#define SAM_DECODE_RID(rid) (((rid) - SAM_MIN_RID) >> 2) +#define SAM_ENCODE_RID(type, id) ((((id) << 2) | type) + SAM_MIN_RID) +#define SAM_ENCODE_UXUID(id) SAM_ENCODE_RID(SAM_RT_UNIX_UID, id) +#define SAM_ENCODE_UXGID(id) SAM_ENCODE_RID(SAM_RT_UNIX_GID, id) +#define SAM_ENCODE_NTUID(id) SAM_ENCODE_RID(SAM_RT_NT_UID, id) +#define SAM_ENCODE_NTGID(id) SAM_ENCODE_RID(SAM_RT_NT_GID, id) + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBMLSVC_H */ diff --git a/usr/src/lib/smbsrv/libmlsvc/common/llib-lmlsvc b/usr/src/lib/smbsrv/libmlsvc/common/llib-lmlsvc new file mode 100644 index 0000000000..412e13d740 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/llib-lmlsvc @@ -0,0 +1,31 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include <smbsrv/libmlsvc.h> diff --git a/usr/src/lib/smbsrv/libmlsvc/common/lmshare.c b/usr/src/lib/smbsrv/libmlsvc/common/lmshare.c new file mode 100644 index 0000000000..5fb02b71f4 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/lmshare.c @@ -0,0 +1,1233 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Lan Manager (SMB/CIFS) share interface implementation. This interface + * returns Win32 error codes, usually network error values (lmerr.h). + */ + +#include <errno.h> +#include <synch.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <syslog.h> +#include <thread.h> +#include <fcntl.h> +#include <unistd.h> +#include <netdb.h> +#include <synch.h> +#include <pthread.h> +#include <sys/mnttab.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/queue.h> +#include <ctype.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libsmbns.h> + +#include <libshare.h> + +#include <smbsrv/lm.h> +#include <smbsrv/lmshare.h> +#include <smbsrv/cifs.h> + +#include <smbsrv/ctype.h> +#include <smbsrv/smb_vops.h> +#include <smbsrv/smb_fsd.h> + +#define LMSHR_HASHTAB_SZ 1024 + +static HT_HANDLE *lmshare_handle = NULL; + +static rwlock_t lmshare_lock; +static pthread_t lmshare_load_thread; +static void *lmshare_load(void *); +static DWORD lmshare_create_table(); +static int lmshare_delete_shmgr(struct lmshare_info *); +static int lmshare_setinfo_shmgr(struct lmshare_info *); +static DWORD lmshare_set_refcnt(char *share_name, int refcnt); + +typedef struct lmshare_ad_item { + TAILQ_ENTRY(lmshare_ad_item) next; + char name[MAXNAMELEN]; + char container[MAXPATHLEN]; + char flag; +} lmshare_ad_item_t; + +typedef struct lmshare_ad_queue { + int nentries; + TAILQ_HEAD(adqueue, lmshare_ad_item) adlist; +} lmshare_ad_queue_t; + +static lmshare_ad_queue_t ad_queue; +static int publish_on = 0; + +static pthread_t lmshare_publish_thread; +static mutex_t lmshare_publish_mutex = PTHREAD_MUTEX_INITIALIZER; +static cond_t lmshare_publish_cv = DEFAULTCV; + +static void *lmshare_publisher(void *); +static void lmshare_stop_publish(); + +/* + * Start loading lmshare information from sharemanager + * and create the cache. + */ +int +lmshare_start() +{ + int rc; + + rc = pthread_create(&lmshare_publish_thread, NULL, + lmshare_publisher, 0); + if (rc != 0) { + syslog(LOG_ERR, "Failed to start publisher thread, " + "share publishing is disabled"); + } + + rc = pthread_create(&lmshare_load_thread, NULL, + lmshare_load, 0); + if (rc != 0) { + syslog(LOG_ERR, "Failed to start share loading, " + "existing shares will not be available"); + } + + return (rc); +} + +void +lmshare_stop() +{ + lmshare_stop_publish(); +} + +/* + * Load shares from sharemanager. + */ +/*ARGSUSED*/ +static void * +lmshare_load(void *args) +{ + lmshare_info_t si; + sa_handle_t handle; + sa_group_t group; + sa_share_t share; + sa_resource_t resource; + sa_optionset_t opts; + char *gstate, *path, *rname; + + if (lmshare_create_table() != NERR_Success) { + syslog(LOG_ERR, "Failed to create share hash table"); + return (NULL); + } + + handle = sa_init(SA_INIT_SHARE_API); + if (handle == NULL) { + syslog(LOG_ERR, "Failed to load share " + "information: no libshare handle"); + return (NULL); + } + + for (group = sa_get_group(handle, NULL); + group != NULL; group = sa_get_next_group(group)) { + gstate = sa_get_group_attr(group, "state"); + if ((gstate == NULL) || + (strcasecmp(gstate, "disabled") == 0)) { + /* Skip disabled or unknown state group */ + continue; + } + /* Check and see if smb protocol is available */ + if (sa_get_optionset(group, SMB_PROTOCOL_NAME) == NULL) + continue; + for (share = sa_get_share(group, NULL); + share != NULL; share = sa_get_next_share(share)) { + path = sa_get_share_attr(share, "path"); + if (path == NULL) { + syslog(LOG_ERR, "Invalid share NO path"); + continue; + } + for (resource = sa_get_share_resource(share, NULL); + resource != NULL; + resource = sa_get_next_resource(resource)) { + rname = sa_get_resource_attr(resource, "name"); + if (rname == NULL) { + syslog(LOG_ERR, "Invalid share " + "resource for path: %s", path); + continue; + } + opts = sa_get_derived_optionset(resource, + SMB_PROTOCOL_NAME, 1); + smb_build_lmshare_info(rname, path, opts, &si); + sa_free_derived_optionset(opts); + (void) free(rname); + if (lmshare_add(&si, 0) != NERR_Success) { + syslog(LOG_ERR, "Failed to load " + "share %s", si.share_name); + } + } + /* We are done with all shares for same path */ + (void) free(path); + } + } + + sa_fini(handle); + + return (NULL); +} + +/* + * lmshare_callback + * + * Call back to free share structures stored + * in shares' hash table. + */ +static void +lmshare_callback(HT_ITEM *item) +{ + if (item && item->hi_data) + (void) free(item->hi_data); +} + +/* + * lmshare_create_table + * + * Create the share hash table. + */ +static DWORD +lmshare_create_table(void) +{ + if (lmshare_handle == NULL) { + (void) rwlock_init(&lmshare_lock, USYNC_THREAD, 0); + (void) rw_wrlock(&lmshare_lock); + + lmshare_handle = ht_create_table(LMSHR_HASHTAB_SZ, + MAXNAMELEN, 0); + if (lmshare_handle == NULL) { + syslog(LOG_ERR, "lmshare_create_table:" + " unable to create share table"); + (void) rw_unlock(&lmshare_lock); + return (NERR_InternalError); + } + (void) ht_register_callback(lmshare_handle, lmshare_callback); + (void) rw_unlock(&lmshare_lock); + } + + return (NERR_Success); +} + +/* + * lmshare_add_adminshare + * + * add the admin share for the volume when the share database + * for that volume is going to be loaded. + */ +DWORD +lmshare_add_adminshare(char *volname, unsigned char drive) +{ + struct lmshare_info si; + DWORD rc; + + if (drive == 0) + return (NERR_InvalidDevice); + + bzero(&si, sizeof (lmshare_info_t)); + (void) strcpy(si.directory, volname); + si.mode = LMSHRM_TRANS; + (void) snprintf(si.share_name, sizeof (si.share_name), "%c$", drive); + rc = lmshare_add(&si, 0); + + return (rc); +} + +/* + * lmshare_num_shares + * + * Return the total number of shares, which should be the same value + * that would be returned from a share enum request. + */ +int +lmshare_num_shares(void) +{ + int n_shares; + + n_shares = ht_get_total_items(lmshare_handle); + + /* If we don't store IPC$ in hash table we should do this */ + n_shares++; + + return (n_shares); +} + +/* + * lmshare_open_iterator + * + * Create and initialize an iterator for traversing hash table. + * It gets a mode that can be LMSHR_IM_ALL to iterate through all + * the shares stored in table or LMSHR_IM_PRES to iterate through + * only presentable shares. + * + * It also accepts a local IP address. This is used in dual head + * systems to only return the shares that belong to the head which + * is specified by the 'ipaddr'. If ipaddr is 0 it'll return shares + * of both heads. + * + * On success return pointer to the new iterator. + * On failure return (NULL). + */ +lmshare_iterator_t * +lmshare_open_iterator(int mode) +{ + lmshare_iterator_t *shi; + int sz = sizeof (lmshare_iterator_t) + sizeof (HT_ITERATOR); + + shi = malloc(sz); + if (shi != NULL) { + bzero(shi, sz); + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + shi->iterator = (HT_ITERATOR *) + ((char *)shi + sizeof (lmshare_iterator_t)); + shi->mode = mode; + } else { + syslog(LOG_DEBUG, "Failed to create share iterator handle"); + } + return (shi); +} + +/* + * lmshare_close_iterator + * + * Free memory allocated by the given iterator. + */ +void +lmshare_close_iterator(lmshare_iterator_t *shi) +{ + (void) free(shi); +} + +/* + * lmshare_iterate + * + * Iterate on the shares in the hash table. The iterator must be opened + * before the first iteration. On subsequent calls, the iterator must be + * passed unchanged. + * + * Returns NULL on failure or when all shares are visited, otherwise + * returns information of visited share. + * + * Note that there are some special shares, i.e. IPC$, that must also + * be processed. + */ +lmshare_info_t * +lmshare_iterate(lmshare_iterator_t *shi) +{ + HT_ITEM *item; + lmshare_info_t *si; + + if (lmshare_handle == NULL || shi == NULL) + return (NULL); + + if (shi->iteration == 0) { + /* + * IPC$ is always first. + */ + (void) strcpy(shi->si.share_name, "IPC$"); + shi->si.mode = LMSHRM_TRANS; + shi->si.stype = (int)(STYPE_IPC | STYPE_SPECIAL); + shi->iteration = 1; + return (&(shi->si)); + } + + if (shi->iteration == 1) { + if ((item = ht_findfirst( + lmshare_handle, shi->iterator)) == NULL) { + return (NULL); + } + + si = (lmshare_info_t *)(item->hi_data); + ++shi->iteration; + + if (si->mode & shi->mode) { + (void) memcpy(&(shi->si), si, + sizeof (lmshare_info_t)); + return (&(shi->si)); + } + } + + while ((item = ht_findnext(shi->iterator)) != NULL) { + si = (lmshare_info_t *)(item->hi_data); + ++shi->iteration; + if (si->mode & shi->mode) { + (void) memcpy(&(shi->si), si, sizeof (lmshare_info_t)); + + return (&(shi->si)); + } + } + + return (NULL); +} + +/* + * lmshare_add + * + * Add a share. This is a wrapper round lmshare_setinfo that checks + * whether or not the share already exists. If the share exists, an + * error is returned. + * + * Don't check lmshare_is_dir here: it causes rootfs to recurse. + */ +DWORD +lmshare_add(lmshare_info_t *si, int doshm) +{ + DWORD status = NERR_Success; + + if (si == 0 || lmshare_is_valid(si->share_name) == 0) + return (NERR_InvalidDevice); + + (void) utf8_strlwr(si->share_name); + + if (lmshare_exists(si->share_name)) { + if ((si->mode & LMSHRM_TRANS) == 0) + return (NERR_DuplicateShare); + } + + if (si->refcnt == 0) { + status = lmshare_setinfo(si, doshm); + lmshare_do_publish(si, LMSHR_PUBLISH, 1); + } + + if ((si->mode & LMSHRM_TRANS) && (status == NERR_Success)) { + si->refcnt++; + status = lmshare_set_refcnt(si->share_name, si->refcnt); + } + + if (status) + return (status); + + return (smb_dwncall_share(LMSHR_ADD, si->directory, si->share_name)); +} + +/* + * lmshare_delete + * + * Remove a share. Ensure that all SMB trees associated with this share + * are disconnected. If the share does not exist, an error is returned. + */ +DWORD +lmshare_delete(char *share_name, int doshm) +{ + lmshare_info_t *si; + HT_ITEM *item; + DWORD status; + char path[MAXPATHLEN]; + + if (share_name) + (void) utf8_strlwr(share_name); + + if (lmshare_is_valid(share_name) == 0 || + lmshare_exists(share_name) == 0) { + return (NERR_NetNameNotFound); + } + + (void) rw_wrlock(&lmshare_lock); + item = ht_find_item(lmshare_handle, share_name); + + if (item == NULL) { + (void) rw_unlock(&lmshare_lock); + return (NERR_ItemNotFound); + } + + si = (lmshare_info_t *)item->hi_data; + if (si == NULL) { + (void) rw_unlock(&lmshare_lock); + return (NERR_InternalError); + } + + if ((si->mode & LMSHRM_TRANS) != 0) { + si->refcnt--; + if (si->refcnt > 0) { + status = lmshare_set_refcnt(si->share_name, si->refcnt); + (void) rw_unlock(&lmshare_lock); + return (status); + } + } + + if (doshm && (lmshare_delete_shmgr(si) != 0)) { + (void) rw_unlock(&lmshare_lock); + return (NERR_InternalError); + } + + lmshare_do_publish(si, LMSHR_UNPUBLISH, 1); + + /* + * Copy the path before the entry is removed from the hash table + */ + + (void) strlcpy(path, si->directory, MAXPATHLEN); + + /* Delete from hash table */ + + (void) ht_remove_item(lmshare_handle, share_name); + (void) rw_unlock(&lmshare_lock); + + return (smb_dwncall_share(LMSHR_DELETE, path, share_name)); +} + +/* + * lmshare_set_refcnt + * + * sets the autohome refcnt for a share + */ +static DWORD +lmshare_set_refcnt(char *share_name, int refcnt) +{ + lmshare_info_t *si; + HT_ITEM *item; + + if (share_name) { + (void) utf8_strlwr(share_name); + } + (void) rw_wrlock(&lmshare_lock); + item = ht_find_item(lmshare_handle, share_name); + if (item == NULL) { + (void) rw_unlock(&lmshare_lock); + return (NERR_ItemNotFound); + } + + si = (lmshare_info_t *)item->hi_data; + if (si == NULL) { + (void) rw_unlock(&lmshare_lock); + return (NERR_InternalError); + } + si->refcnt = refcnt; + (void) rw_unlock(&lmshare_lock); + return (NERR_Success); +} + +/* + * lmshare_rename + * + * Rename a share. Check that the current name exists and the new name + * doesn't exist. The rename is performed by deleting the current share + * definition and creating a new share with the new name. + */ +DWORD +lmshare_rename(char *from_name, char *to_name, int doshm) +{ + struct lmshare_info si; + DWORD nerr; + + if (lmshare_is_valid(from_name) == 0 || + lmshare_is_valid(to_name) == 0) + return (NERR_InvalidDevice); + + (void) utf8_strlwr(from_name); + (void) utf8_strlwr(to_name); + + if (lmshare_exists(from_name) == 0) + return (NERR_NetNameNotFound); + + if (lmshare_exists(to_name)) + return (NERR_DuplicateShare); + + if ((nerr = lmshare_getinfo(from_name, &si)) != NERR_Success) + return (nerr); + + if ((nerr = lmshare_delete(from_name, doshm)) != NERR_Success) + return (nerr); + + (void) strlcpy(si.share_name, to_name, MAXNAMELEN); + return (lmshare_add(&si, 1)); +} + +/* + * lmshare_exists + * + * Returns 1 if the share exists. Otherwise returns 0. + */ +int +lmshare_exists(char *share_name) +{ + if (share_name == 0 || *share_name == 0) + return (0); + + if (ht_find_item(lmshare_handle, share_name) == NULL) + return (0); + else + return (1); +} + +/* + * lmshare_is_special + * + * Simple check to determine if share name represents a special share, + * i.e. the last character of the name is a '$'. Returns STYPE_SPECIAL + * if the name is special. Otherwise returns 0. + */ +int +lmshare_is_special(char *share_name) +{ + int len; + + if (share_name == 0) + return (0); + + if ((len = strlen(share_name)) == 0) + return (0); + + if (share_name[len - 1] == '$') + return (STYPE_SPECIAL); + else + return (0); +} + + +/* + * lmshare_is_restricted + * + * Check whether or not there is a restriction on a share. Restricted + * shares are generally STYPE_SPECIAL, for example, IPC$. All the + * administration share names are restricted: C$, D$ etc. Returns 1 + * if the share is restricted. Otherwise 0 is returned to indicate + * that there are no restrictions. + */ +int +lmshare_is_restricted(char *share_name) +{ + static char *restricted[] = { + "IPC$" + }; + + int i; + + for (i = 0; i < sizeof (restricted)/sizeof (restricted[0]); i++) { + if (strcasecmp(restricted[i], share_name) == 0) + return (1); + } + + if (lmshare_is_admin(share_name)) + return (1); + + return (0); +} + + +/* + * lmshare_is_admin + * + * Check whether or not access to the share should be restricted to + * administrators. This is a bit of a hack because what we're doing + * is checking for the default admin shares: C$, D$ etc.. There are + * other shares that have restrictions: see lmshare_is_restricted(). + * + * Returns 1 if the shares is an admin share. Otherwise 0 is returned + * to indicate that there are no restrictions. + */ +int +lmshare_is_admin(char *share_name) +{ + if (share_name == 0) + return (0); + + if (strlen(share_name) == 2 && + mts_isalpha(share_name[0]) && share_name[1] == '$') { + return (1); + } + + return (0); +} + + +/* + * lmshare_is_valid + * + * Check if any invalid char is present in share name. According to + * MSDN article #236388: "Err Msg: The Share Name Contains Invalid + * Characters", the list of invalid character is: + * + * " / \ [ ] : | < > + ; , ? * = + * + * Also rejects if control characters are embedded. + * + * If the sharename is valid, return (1). Otherwise return (0). + */ +int +lmshare_is_valid(char *share_name) +{ + char *invalid = "\"/\\[]:|<>+;,?*="; + char *cp; + + if (share_name == 0) + return (0); + + if (strpbrk(share_name, invalid)) + return (0); + + for (cp = share_name; *cp != '\0'; cp++) + if (iscntrl(*cp)) + return (0); + + return (1); +} + +/* + * lmshare_is_dir + * + * Check to determine if a share object represents a directory. + * + * Returns 1 if the path leads to a directory. Otherwise returns 0. + */ +int +lmshare_is_dir(char *path) +{ + struct stat stat_info; + + if (stat(path, &stat_info) == 0) + if (S_ISDIR(stat_info.st_mode)) + return (1); + + return (0); + +} + +/* + * lmshare_getinfo + * + * Load the information for the specified share into the supplied share + * info structure. If the shared directory does not begin with a /, one + * will be inserted as a prefix. + */ +DWORD +lmshare_getinfo(char *share_name, struct lmshare_info *si) +{ + int i, endidx; + int dirlen; + HT_ITEM *item; + + (void) rw_rdlock(&lmshare_lock); + + (void) utf8_strlwr(share_name); + if ((item = ht_find_item(lmshare_handle, share_name)) == NULL) { + bzero(si, sizeof (lmshare_info_t)); + (void) rw_unlock(&lmshare_lock); + return (NERR_NetNameNotFound); + } + + (void) memcpy(si, item->hi_data, sizeof (lmshare_info_t)); + (void) rw_unlock(&lmshare_lock); + + if (si->directory[0] == '\0') + return (NERR_NetNameNotFound); + + if (si->directory[0] != '/') { + dirlen = strlen(si->directory) + 1; + endidx = (dirlen < MAXPATHLEN-1) ? + dirlen : MAXPATHLEN - 2; + for (i = endidx; i >= 0; i--) + si->directory[i+1] = si->directory[i]; + si->directory[MAXPATHLEN-1] = '\0'; + si->directory[0] = '/'; + } + + return (NERR_Success); +} + +/* + * Remove share from sharemanager repository. + */ +static int +lmshare_delete_shmgr(struct lmshare_info *si) +{ + sa_handle_t handle; + sa_share_t share; + sa_resource_t resource; + + handle = sa_init(SA_INIT_SHARE_API); + if (handle == NULL) { + syslog(LOG_ERR, "Failed to get handle to " + "share lib"); + return (1); + } + share = sa_find_share(handle, si->directory); + if (share == NULL) { + syslog(LOG_ERR, "Failed to get share to delete"); + sa_fini(handle); + return (1); + } + resource = sa_get_share_resource(share, si->share_name); + if (resource == NULL) { + syslog(LOG_ERR, "Failed to get share resource to delete"); + sa_fini(handle); + return (1); + } + if (sa_remove_resource(resource) != SA_OK) { + syslog(LOG_ERR, "Failed to remove resource"); + sa_fini(handle); + return (1); + } + sa_fini(handle); + return (0); +} + +static int +lmshare_setinfo_shmgr(struct lmshare_info *si) +{ + sa_handle_t handle; + sa_share_t share; + sa_group_t group; + sa_resource_t resource; + int share_created = 0; + int err; + + /* Add share to sharemanager */ + handle = sa_init(SA_INIT_SHARE_API); + if (handle == NULL) { + syslog(LOG_ERR, "Failed to get handle to share lib"); + return (1); + } + share = sa_find_share(handle, si->directory); + if (share == NULL) { + group = smb_get_smb_share_group(handle); + if (group == NULL) { + sa_fini(handle); + return (1); + } + share = sa_add_share(group, si->directory, 0, &err); + if (share == NULL) { + sa_fini(handle); + return (1); + } + share_created = 1; + } + resource = sa_get_share_resource(share, si->share_name); + if (resource == NULL) { + resource = sa_add_resource(share, si->share_name, + SA_SHARE_PERMANENT, &err); + if (resource == NULL) { + goto failure; + } + } + if (sa_set_resource_attr(resource, + "description", si->comment) != SA_OK) { + syslog(LOG_ERR, "Falied to set resource " + "description in sharemgr"); + goto failure; + } + if (sa_set_resource_attr(resource, + SHOPT_AD_CONTAINER, si->container) != SA_OK) { + syslog(LOG_ERR, "Falied to set ad-container in sharemgr"); + goto failure; + } + + sa_fini(handle); + return (0); +failure: + if (share_created && (share != NULL)) { + if (sa_remove_share(share) != SA_OK) { + syslog(LOG_ERR, "Failed to cleanup share"); + } + } + if (resource != NULL) { + if (sa_remove_resource(resource) != SA_OK) { + syslog(LOG_ERR, "Failed to cleanup share resource"); + } + } + sa_fini(handle); + return (1); +} + +/* + * del_fromhash + * + * Delete the given share only from hash table + */ +static DWORD +del_fromhash(char *share_name) +{ + if (share_name == 0) + return (NERR_NetNameNotFound); + + (void) utf8_strlwr(share_name); + + if (lmshare_is_valid(share_name) == 0 || + lmshare_exists(share_name) == 0) { + return (NERR_NetNameNotFound); + } + + (void) rw_wrlock(&lmshare_lock); + (void) ht_remove_item(lmshare_handle, share_name); + (void) rw_unlock(&lmshare_lock); + + return (NERR_Success); +} + +/* + * lmshare_setinfo + * + * Adds the specified share into the system hash table + * and also store its info in the corresponding disk + * structure if it is not a temporary (LMSHRM_TRANS) share. + * when the first share is going to be added, create shares + * hash table if it is not already created. + * If the share already exists, it will be replaced. If the + * new share directory name does not begin with a /, one will be + * inserted as a prefix. + */ +DWORD +lmshare_setinfo(lmshare_info_t *si, int doshm) +{ + int i, endidx; + int dirlen; + lmshare_info_t *add_si; + int res = NERR_Success; + lmshare_info_t old_si; + + if (si->directory[0] != '/') { + dirlen = strlen(si->directory) + 1; + endidx = (dirlen < MAXPATHLEN - 1) ? + dirlen : MAXPATHLEN - 2; + for (i = endidx; i >= 0; i--) + si->directory[i+1] = si->directory[i]; + si->directory[MAXPATHLEN-1] = '\0'; + si->directory[0] = '/'; + } + + /* XXX Do we need to translate the directory here? to real path */ + if (lmshare_is_dir(si->directory) == 0) + return (NERR_UnknownDevDir); + + /* + * We should allocate memory for new entry because we + * don't know anything about the passed pointer i.e. + * it maybe destroyed by caller of this function while + * we only store a pointer to the data in hash table. + * Hash table doesn't do any allocation for the data that + * is being added. + */ + add_si = malloc(sizeof (lmshare_info_t)); + if (add_si == NULL) { + syslog(LOG_ERR, "LmshareSetinfo: resource shortage"); + return (NERR_NoRoom); + } + + (void) memcpy(add_si, si, sizeof (lmshare_info_t)); + + /* + * If we can't find it, use the new one to get things in sync, + * but if there is an existing one, that is the one to + * unpublish. + */ + if (lmshare_getinfo(si->share_name, &old_si) != NERR_Success) + (void) memcpy(&old_si, si, sizeof (lmshare_info_t)); + + if (doshm) { + res = lmshare_delete(si->share_name, doshm); + if (res != NERR_Success) { + free(add_si); + syslog(LOG_ERR, "LmshareSetinfo: delete failed", res); + return (res); + } + } else { + /* Unpublish old share from AD */ + if ((si->mode & LMSHRM_TRANS) == 0) { + lmshare_do_publish(&old_si, LMSHR_UNPUBLISH, 1); + } + (void) del_fromhash(si->share_name); + } + /* if it's not transient it should be permanent */ + if ((add_si->mode & LMSHRM_TRANS) == 0) + add_si->mode |= LMSHRM_PERM; + + + add_si->stype = STYPE_DISKTREE; + add_si->stype |= lmshare_is_special(add_si->share_name); + + (void) rw_wrlock(&lmshare_lock); + if (ht_add_item(lmshare_handle, add_si->share_name, add_si) == NULL) { + syslog(LOG_ERR, "lmshare_setinfo[%s]: error in adding share", + add_si->share_name); + (void) rw_unlock(&lmshare_lock); + free(add_si); + return (NERR_InternalError); + } + (void) rw_unlock(&lmshare_lock); + + if ((add_si->mode & LMSHRM_TRANS) == 0) { + if (doshm && (lmshare_setinfo_shmgr(add_si) != 0)) { + syslog(LOG_ERR, "Update share %s in sharemgr failed", + add_si->share_name); + return (NERR_InternalError); + } + lmshare_do_publish(add_si, LMSHR_PUBLISH, 1); + } + + return (res); +} + +/* + * lmshare_decode_type + * + * Gets a SMB share type as an integer value and return + * a string name for it. + */ +static char * +lmshare_decode_type(uint_t stype) +{ + switch (stype) { + case STYPE_DISKTREE: + return ("Disk"); + case STYPE_PRINTQ: + return ("Print Queue"); + case STYPE_DEVICE: + return ("Device"); + case STYPE_IPC: + return ("IPC"); + case STYPE_DFS: + return ("DFS"); + case STYPE_SPECIAL: + return ("Special"); + default: + return ("Unknown"); + /* NOTREACHED */ + }; +} + +/* + * lmshare_loginfo + * + * Decodes and writes the information of the given + * share to the specified file. + */ +void +lmshare_loginfo(FILE *fp, lmshare_info_t *si) +{ + (void) fprintf(fp, "\n%s Information:\n", si->share_name); + (void) fprintf(fp, "\tFolder: %s\n", si->directory); + (void) fprintf(fp, "\tType: %s\n", + lmshare_decode_type((uint_t)si->stype)); + (void) fprintf(fp, "\tComment: %s\n", si->comment); + + (void) fprintf(fp, "\tStatus: %s\n", + ((si->mode & LMSHRM_TRANS) ? "Transient" : "Permanent")); + + (void) fprintf(fp, "\tContainer: %s\n", si->container); +} + +DWORD +lmshare_list(int offset, lmshare_list_t *list) +{ + lmshare_iterator_t *iterator; + lmshare_info_t *si; + int list_idx = 0; + int i = 0; + + bzero(list, sizeof (lmshare_list_t)); + if ((iterator = lmshare_open_iterator(LMSHRM_ALL)) == NULL) + return (NERR_InternalError); + + (void) lmshare_iterate(iterator); /* To skip IPC$ */ + + while ((si = lmshare_iterate(iterator)) != NULL) { + if (lmshare_is_special(si->share_name)) { + /* + * Don't return restricted shares. + */ + if (lmshare_is_restricted(si->share_name)) + continue; + } + + if (i++ < offset) + continue; + + (void) memcpy(&list->smbshr[list_idx], si, + sizeof (lmshare_info_t)); + if (++list_idx == LMSHARES_PER_REQUEST) + break; + } + lmshare_close_iterator(iterator); + + list->no = list_idx; + + return (NERR_Success); +} + +/* + * Put the share on publish queue. + */ +void +lmshare_do_publish(lmshare_info_t *si, char flag, int poke) +{ + lmshare_ad_item_t *item = NULL; + + if (publish_on == 0) + return; + if ((si == NULL) || (si->container[0] == '\0')) + return; + (void) mutex_lock(&lmshare_publish_mutex); + item = (lmshare_ad_item_t *)malloc(sizeof (lmshare_ad_item_t)); + if (item == NULL) { + syslog(LOG_ERR, "Failed to allocate share publish item"); + (void) mutex_unlock(&lmshare_publish_mutex); + return; + } + item->flag = flag; + (void) strlcpy(item->name, si->share_name, sizeof (item->name)); + (void) strlcpy(item->container, si->container, + sizeof (item->container)); + /*LINTED - E_CONSTANT_CONDITION*/ + TAILQ_INSERT_TAIL(&ad_queue.adlist, item, next); + ad_queue.nentries++; + if (poke) + (void) cond_signal(&lmshare_publish_cv); + (void) mutex_unlock(&lmshare_publish_mutex); +} + +void +lmshare_stop_publish() +{ + (void) mutex_lock(&lmshare_publish_mutex); + publish_on = 0; + (void) cond_signal(&lmshare_publish_cv); + (void) mutex_unlock(&lmshare_publish_mutex); +} + +/* + * This functions waits to be signaled and once running + * will publish/unpublish any items on list. + * lmshare_stop_publish when called will exit this thread. + */ +/*ARGSUSED*/ +static void * +lmshare_publisher(void *arg) +{ + ADS_HANDLE *ah; + lmshare_ad_item_t *item; + char hostname[MAXHOSTNAMELEN]; + char name[MAXNAMELEN]; + char container[MAXPATHLEN]; + char flag; + + /*LINTED - E_CONSTANT_CONDITION*/ + TAILQ_INIT(&ad_queue.adlist); + ad_queue.nentries = 0; + publish_on = 1; + hostname[0] = '\0'; + + for (;;) { + (void) cond_wait(&lmshare_publish_cv, + &lmshare_publish_mutex); + + if (hostname[0] == '\0') { + if (smb_gethostname(hostname, MAXHOSTNAMELEN, 0) != 0) + continue; + } + + if (publish_on == 0) { + syslog(LOG_DEBUG, "lmshare: publisher exit"); + if (ad_queue.nentries == 0) { + (void) mutex_unlock(&lmshare_publish_mutex); + break; + } + for (item = TAILQ_FIRST(&ad_queue.adlist); item; + item = TAILQ_FIRST(&ad_queue.adlist)) { + /*LINTED - E_CONSTANT_CONDITION*/ + TAILQ_REMOVE(&ad_queue.adlist, item, next); + (void) free(item); + } + ad_queue.nentries = 0; + (void) mutex_unlock(&lmshare_publish_mutex); + break; + } + if (ad_queue.nentries == 0) + continue; + ah = ads_open(); + if (ah == NULL) { + /* We mostly have no AD config so just clear the list */ + for (item = TAILQ_FIRST(&ad_queue.adlist); item; + item = TAILQ_FIRST(&ad_queue.adlist)) { + /*LINTED - E_CONSTANT_CONDITION*/ + TAILQ_REMOVE(&ad_queue.adlist, item, next); + (void) free(item); + } + ad_queue.nentries = 0; + continue; + } + TAILQ_FOREACH(item, &ad_queue.adlist, next) { + (void) strlcpy(name, item->name, sizeof (name)); + (void) strlcpy(container, item->container, + sizeof (container)); + flag = item->flag; + + if (flag == LMSHR_UNPUBLISH) + (void) ads_remove_share(ah, name, NULL, + container, hostname); + else + (void) ads_publish_share(ah, name, NULL, + container, hostname); + } + for (item = TAILQ_FIRST(&ad_queue.adlist); item; + item = TAILQ_FIRST(&ad_queue.adlist)) { + /*LINTED - E_CONSTANT_CONDITION*/ + TAILQ_REMOVE(&ad_queue.adlist, item, next); + (void) free(item); + } + ad_queue.nentries = 0; + if (ah != NULL) { + ads_close(ah); + ah = NULL; + } + } + + syslog(LOG_DEBUG, "lmshare: Stopping publisher"); + return (NULL); +} + +/* + * lmshare_get_realpath + * + * Derive the real path of a share from the path provided by a + * Windows client application during the share addition. + * + * For instance, the real path of C:\ is /cvol and the + * real path of F:\home is /vol1/home. + * + * clipath - path provided by the Windows client is in the + * format of <drive letter>:\<dir> + * realpath - path that will be stored as the directory field of + * the lmshare_info_t structure of the share. + * maxlen - maximum length fo the realpath buffer + * + * Return LAN Manager network error code. + */ +/*ARGSUSED*/ +DWORD +lmshare_get_realpath(const char *clipath, char *realpath, int maxlen) +{ + /* XXX do this translation */ + return (NERR_Success); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/lsalib.c b/usr/src/lib/smbsrv/libmlsvc/common/lsalib.c new file mode 100644 index 0000000000..ca98eb8eab --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/lsalib.c @@ -0,0 +1,637 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module provides the high level interface to the LSA RPC functions. + */ + +#include <strings.h> +#include <unistd.h> +#include <netdb.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libsmbns.h> +#include <smbsrv/libmlsvc.h> +#include <smbsrv/lsalib.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/ntsid.h> +#include <smbsrv/smb_token.h> + +static int lsa_list_accounts(mlsvc_handle_t *); + +/* + * lsa_query_primary_domain_info + * + * Obtains the primary domain SID and name from the specified server + * (domain controller). The information is stored in the NT domain + * database by the lower level lsar_query_info_policy call. The caller + * should query the database to obtain a reference to the primary + * domain information. + * + * Returns NT status codes. + */ +DWORD +lsa_query_primary_domain_info(void) +{ + mlsvc_handle_t domain_handle; + DWORD status; + + if ((lsar_open(MLSVC_IPC_ANON, 0, 0, 0, 0, &domain_handle)) != 0) + return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + + status = lsar_query_info_policy(&domain_handle, + MSLSA_POLICY_PRIMARY_DOMAIN_INFO); + + (void) lsar_close(&domain_handle); + return (status); +} + +/* + * lsa_query_account_domain_info + * + * Obtains the account domain SID and name from the current server + * (domain controller). The information is stored in the NT domain + * database by the lower level lsar_query_info_policy call. The caller + * should query the database to obtain a reference to the account + * domain information. + * + * Returns NT status codes. + */ +DWORD +lsa_query_account_domain_info(void) +{ + mlsvc_handle_t domain_handle; + DWORD status; + + if ((lsar_open(MLSVC_IPC_ANON, 0, 0, 0, 0, &domain_handle)) != 0) + return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + + status = lsar_query_info_policy(&domain_handle, + MSLSA_POLICY_ACCOUNT_DOMAIN_INFO); + + (void) lsar_close(&domain_handle); + return (status); +} + +/* + * lsa_enum_trusted_domains + * + * Enumerate the trusted domains in our primary domain. The information + * is stored in the NT domain database by the lower level + * lsar_enum_trusted_domains call. The caller should query the database + * to obtain a reference to the trusted domain information. + * + * Returns NT status codes. + */ +DWORD +lsa_enum_trusted_domains(void) +{ + mlsvc_handle_t domain_handle; + DWORD enum_context; + DWORD status; + + if ((lsar_open(MLSVC_IPC_ANON, 0, 0, 0, 0, &domain_handle)) != 0) + return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + + enum_context = 0; + + status = lsar_enum_trusted_domains(&domain_handle, &enum_context); + if (status == MLSVC_NO_MORE_DATA) { + /* + * MLSVC_NO_MORE_DATA indicates that we + * have all of the available information. + */ + status = NT_STATUS_SUCCESS; + } + + (void) lsar_close(&domain_handle); + return (status); +} + +/* + * lsa_test_lookup + * + * Test routine for lsa_lookup_name and lsa_lookup_sid. + */ +void +lsa_test_lookup(char *name) +{ + smb_userinfo_t *user_info; + nt_sid_t *sid; + DWORD status; + smb_ntdomain_t *di; + + if ((di = smb_getdomaininfo(0)) == 0) + return; + + user_info = mlsvc_alloc_user_info(); + + if (lsa_lookup_builtin_name(name, user_info) != 0) { + status = lsa_lookup_name(di->server, di->domain, name, + user_info); + + if (status == 0) { + sid = nt_sid_splice(user_info->domain_sid, + user_info->rid); + + (void) lsa_lookup_sid(sid, user_info); + free(sid); + } + } + + mlsvc_free_user_info(user_info); +} + +/* + * lsa_lookup_builtin_name + * + * lookup builtin account table to see if account_name is + * there. If it is there, set sid_name_use, domain_sid, + * domain_name, and rid fields of the passed user_info + * structure and return 0. If lookup fails return 1. + */ +int +lsa_lookup_builtin_name(char *account_name, smb_userinfo_t *user_info) +{ + char *domain; + int res; + + user_info->domain_sid = nt_builtin_lookup_name(account_name, + &user_info->sid_name_use); + + if (user_info->domain_sid == 0) + return (1); + + res = nt_sid_split(user_info->domain_sid, &user_info->rid); + if (res < 0) + return (1); + + domain = nt_builtin_lookup_domain(account_name); + if (domain) { + user_info->domain_name = strdup(domain); + return (0); + } + + return (1); +} + +/* + * lsa_lookup_local_sam + * + * lookup for the given account name in the local SAM database. + * Returns 0 on success. If lookup fails return 1. + */ +int +lsa_lookup_local_sam(char *domain, char *account_name, + smb_userinfo_t *user_info) +{ + nt_group_t *grp; + + if (*domain == '\0' || *account_name == '\0') + return (1); + + grp = nt_group_getinfo(account_name, RWLOCK_READER); + if (grp == 0) + return (1); + + user_info->sid_name_use = *grp->sid_name_use; + user_info->domain_sid = nt_sid_dup(grp->sid); + nt_group_putinfo(grp); + + if (user_info->domain_sid == 0) + return (1); + + (void) nt_sid_split(user_info->domain_sid, &user_info->rid); + user_info->domain_name = strdup(domain); + + if (user_info->domain_name == 0) { + free(user_info->domain_sid); + user_info->domain_sid = 0; + return (1); + } + + return (0); +} + +/* + * lsa_lookup_local + * + * if given account name has domain part, check to see if + * it matches with host name or any of host's primary addresses. + * if any match found first lookup in builtin accounts table and + * then in local SAM table. + * + * if account name doesn't have domain part, first do local lookups + * if nothing is found return 1. This means that caller function should + * do domain lookup. + * if any error happened return -1, if name is found return 0. + */ +int +lsa_lookup_local(char *name, smb_userinfo_t *user_info) +{ + char hostname[MAXHOSTNAMELEN]; + int res = 0; + int local_lookup = 0; + char *tmp; + net_cfg_t cfg; + uint32_t addr; + + if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0) + return (-1); + + tmp = strchr(name, '\\'); + if (tmp != 0) { + *tmp = 0; + if (strcasecmp(name, hostname) == 0) + local_lookup = 1; + + if (!local_lookup) { + addr = inet_addr(name); + if (smb_nic_get_byip(addr, &cfg) != NULL) { + local_lookup = 1; + } + } + + if (!local_lookup) { + /* do domain lookup */ + *tmp = '\\'; + return (1); + } + + name = tmp + 1; + local_lookup = 1; + } + + res = lsa_lookup_builtin_name(name, user_info); + if (res != 0) + res = lsa_lookup_local_sam(hostname, name, user_info); + + if (res == 0) + return (0); + + if (local_lookup) + return (-1); + + return (1); +} + +/* + * lsa_lookup_name + * + * Lookup a name on the specified server (domain controller) and obtain + * the appropriate SID. The information is returned in the user_info + * structure. The caller is responsible for allocating and releasing + * this structure. On success sid_name_use will be set to indicate the + * type of SID. If the name is the domain name, this function will be + * identical to lsa_domain_info. Otherwise the rid and name fields will + * also be valid. On failure sid_name_use will be set to SidTypeUnknown. + * + * On success 0 is returned. Otherwise a -ve error code. + */ +int lsa_lookup_name(char *server, char *domain, char *account_name, + smb_userinfo_t *user_info) +{ + mlsvc_handle_t domain_handle; + int rc; + + rc = lsar_open(MLSVC_IPC_ANON, server, domain, 0, 0, &domain_handle); + if (rc != 0) + return (-1); + + rc = lsar_lookup_names(&domain_handle, account_name, user_info); + + (void) lsar_close(&domain_handle); + return (rc); +} + +/* + * lsa_lookup_name2 + * + * Returns NT status codes. + */ +DWORD lsa_lookup_name2(char *server, char *domain, char *account_name, + smb_userinfo_t *user_info) +{ + mlsvc_handle_t domain_handle; + DWORD status; + int rc; + + rc = lsar_open(MLSVC_IPC_ANON, server, domain, 0, 0, &domain_handle); + if (rc != 0) + return (NT_STATUS_INVALID_PARAMETER); + + status = lsar_lookup_names2(&domain_handle, account_name, user_info); + if (status == NT_STATUS_REVISION_MISMATCH) { + /* + * Not a Windows 2000 domain controller: + * use the NT compatible call. + */ + if (lsar_lookup_names(&domain_handle, account_name, + user_info) != 0) + status = NT_STATUS_NONE_MAPPED; + else + status = 0; + } + + (void) lsar_close(&domain_handle); + return (status); +} + +/* + * lsa_lookup_sid + * + * Lookup a SID on the specified server (domain controller) and obtain + * the appropriate name. The information is returned in the user_info + * structure. The caller is responsible for allocating and releasing + * this structure. On success sid_name_use will be set to indicate the + * type of SID. On failure sid_name_use will be set to SidTypeUnknown. + * + * On success 0 is returned. Otherwise a -ve error code. + */ +int +lsa_lookup_sid(nt_sid_t *sid, smb_userinfo_t *user_info) +{ + mlsvc_handle_t domain_handle; + int rc; + + rc = lsar_open(MLSVC_IPC_ANON, 0, 0, 0, 0, &domain_handle); + if (rc != 0) + return (-1); + + rc = lsar_lookup_sids(&domain_handle, + (struct mslsa_sid *)sid, user_info); + + (void) lsar_close(&domain_handle); + return (rc); +} + +/* + * lsa_lookup_sid2 + * + * Returns NT status codes. + */ +DWORD +lsa_lookup_sid2(nt_sid_t *sid, smb_userinfo_t *user_info) +{ + mlsvc_handle_t domain_handle; + DWORD status; + int rc; + + rc = lsar_open(MLSVC_IPC_ANON, 0, 0, 0, 0, &domain_handle); + if (rc != 0) + return (NT_STATUS_INVALID_PARAMETER); + + status = lsar_lookup_sids2(&domain_handle, + (struct mslsa_sid *)sid, user_info); + + if (status == NT_STATUS_REVISION_MISMATCH) { + /* + * Not a Windows 2000 domain controller: + * use the NT compatible call. + */ + if (lsar_lookup_sids(&domain_handle, (struct mslsa_sid *)sid, + user_info) != 0) + status = NT_STATUS_NONE_MAPPED; + else + status = 0; + } + + (void) lsar_close(&domain_handle); + return (status); +} + +/* + * lsa_test_lookup2 + * + * Test routine for lsa_lookup_name2 and lsa_lookup_sid2. + */ +void +lsa_test_lookup2(char *name) +{ + smb_userinfo_t *user_info; + nt_sid_t *sid; + DWORD status; + smb_ntdomain_t *di; + + if ((di = smb_getdomaininfo(0)) == 0) + return; + + user_info = mlsvc_alloc_user_info(); + + if (lsa_lookup_builtin_name(name, user_info) != 0) { + status = lsa_lookup_name2(di->server, di->domain, name, + user_info); + + if (status == 0) { + sid = nt_sid_splice(user_info->domain_sid, + user_info->rid); + + (void) lsa_lookup_sid2(sid, user_info); + free(sid); + } + } + + mlsvc_free_user_info(user_info); +} + +/* + * lsa_lookup_privs + * + * Request the privileges associated with the specified account. In + * order to get the privileges, we first have to lookup the name on + * the specified domain controller and obtain the appropriate SID. + * The SID can then be used to open the account and obtain the + * account privileges. The results from both the name lookup and the + * privileges are returned in the user_info structure. The caller is + * responsible for allocating and releasing this structure. + * + * On success 0 is returned. Otherwise a -ve error code. + */ +/*ARGSUSED*/ +int +lsa_lookup_privs(char *server, char *account_name, char *target_name, + smb_userinfo_t *user_info) +{ + mlsvc_handle_t domain_handle; + int rc; +#if 0 + mlsvc_handle_t account_handle; + struct mslsa_sid *sid; + + lsa_lookup_name(0, 0, target_name, user_info); + + sid = (struct mslsa_sid *) + nt_sid_splice(user_info->domain_sid, user_info->rid); + + lsa_lookup_sid(server, account_name, (nt_sid_t *)sid, user_info); +#endif + if ((lsar_open(MLSVC_IPC_ANON, 0, 0, 0, 0, &domain_handle)) != 0) + return (-1); + + rc = lsa_list_accounts(&domain_handle); +#if 0 + rc = lsar_open_account(&domain_handle, sid, &account_handle); + if (rc == 0) { + (void) lsar_enum_privs_account(&account_handle, user_info); + (void) lsar_close(&account_handle); + } + + free(sid); +#endif + (void) lsar_close(&domain_handle); + return (rc); +} + +/* + * lsa_list_privs + * + * List the privileges supported by the specified server. + * This function is only intended for diagnostics. + * + * Returns NT status codes. + */ +DWORD +lsa_list_privs(char *server, char *domain) +{ + static char name[128]; + static struct ms_luid luid; + mlsvc_handle_t domain_handle; + int rc; + int i; + + rc = lsar_open(MLSVC_IPC_ANON, server, domain, 0, 0, &domain_handle); + if (rc != 0) + return (NT_STATUS_INVALID_PARAMETER); + + for (i = 0; i < 30; ++i) { + luid.low_part = i; + rc = lsar_lookup_priv_name(&domain_handle, &luid, name, 128); + if (rc != 0) + continue; + + (void) lsar_lookup_priv_value(&domain_handle, name, &luid); + (void) lsar_lookup_priv_display_name(&domain_handle, name, + name, 128); + } + + (void) lsar_close(&domain_handle); + return (NT_STATUS_SUCCESS); +} + +/* + * lsa_test + * + * LSA test routine: open and close the LSA interface. + * TBD: the parameters should be server and domain. + * + * On success 0 is returned. Otherwise a -ve error code. + */ +/*ARGSUSED*/ +int +lsa_test(char *server, char *account_name) +{ + mlsvc_handle_t domain_handle; + int rc; + + rc = lsar_open(MLSVC_IPC_ANON, 0, 0, 0, 0, &domain_handle); + if (rc != 0) + return (-1); + + if (lsar_close(&domain_handle) != 0) + return (-1); + + return (0); +} + +/* + * lsa_list_accounts + * + * This function can be used to list the accounts in the specified + * domain. For now the SIDs are just listed in the system log. + * + * On success 0 is returned. Otherwise a -ve error code. + */ +static int +lsa_list_accounts(mlsvc_handle_t *domain_handle) +{ + mlsvc_handle_t account_handle; + struct mslsa_EnumAccountBuf accounts; + struct mslsa_sid *sid; + char *name; + WORD sid_name_use; + smb_userinfo_t *user_info; + DWORD enum_context = 0; + int rc; + int i; + + user_info = mlsvc_alloc_user_info(); + bzero(&accounts, sizeof (struct mslsa_EnumAccountBuf)); + + do { + rc = lsar_enum_accounts(domain_handle, &enum_context, + &accounts); + if (rc != 0) + return (rc); + + for (i = 0; i < accounts.entries_read; ++i) { + sid = accounts.info[i].sid; + + name = nt_builtin_lookup_sid((nt_sid_t *)sid, + &sid_name_use); + + if (name == 0) { + if (lsar_lookup_sids(domain_handle, sid, + user_info) == 0) { + name = user_info->name; + sid_name_use = user_info->sid_name_use; + } else { + name = "unknown"; + sid_name_use = SidTypeUnknown; + } + } + + nt_sid_logf((nt_sid_t *)sid); + + if (lsar_open_account(domain_handle, sid, + &account_handle) == 0) { + (void) lsar_enum_privs_account(&account_handle, + user_info); + (void) lsar_close(&account_handle); + } + + free(accounts.info[i].sid); + mlsvc_release_user_info(user_info); + } + + if (accounts.info) + free(accounts.info); + } while (rc == 0 && accounts.entries_read != 0); + + mlsvc_free_user_info(user_info); + return (0); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/lsar_lookup.c b/usr/src/lib/smbsrv/libmlsvc/common/lsar_lookup.c new file mode 100644 index 0000000000..5216818cce --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/lsar_lookup.c @@ -0,0 +1,875 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Local Security Authority RPC (LSARPC) library interface functions for + * query, lookup and enumeration calls. + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <sys/errno.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ntaccess.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/ntlocale.h> +#include <smbsrv/lsalib.h> +#include <smbsrv/string.h> +#include <smbsrv/mlsvc.h> + +/* + * The maximum number of bytes we are prepared to deal with in a + * response. + */ +#define MLSVC_MAX_RESPONSE_LEN 1024 + +/* + * This structure is used when lookuping up names. We only lookup one + * name at a time but the structure will allow for more. + */ +typedef struct lookup_name_table { + DWORD n_entry; + mslsa_string_t name[8]; +} lookup_name_table_t; + +/* + * lsar_query_security_desc + * + * Don't use this call yet. It is just a place holder for now. + */ +int +lsar_query_security_desc(mlsvc_handle_t *lsa_handle) +{ + struct mslsa_QuerySecurityObject arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int rc; + int opnum; + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_QuerySecurityObject; + + bzero(&arg, sizeof (struct mslsa_QuerySecurityObject)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * lsar_query_info_policy + * + * The general purpose of this function is to allow various pieces of + * information to be queried on the domain controller. The only + * information queries supported are MSLSA_POLICY_PRIMARY_DOMAIN_INFO + * and MSLSA_POLICY_ACCOUNT_DOMAIN_INFO. + * + * On success, the return code will be 0 and the user_info structure + * will be set up. The sid_name_use field will be set to SidTypeDomain + * indicating that the domain name and domain sid fields are vaild. If + * the infoClass returned from the server is not one of the supported + * values, the sid_name_use willbe set to SidTypeUnknown. If the RPC + * fails, a negative error code will be returned, in which case the + * user_info will not have been updated. + */ +DWORD +lsar_query_info_policy(mlsvc_handle_t *lsa_handle, WORD infoClass) +{ + struct mslsa_QueryInfoPolicy arg; + struct mlsvc_rpc_context *context; + struct mslsa_PrimaryDomainInfo *pd_info; + struct mslsa_AccountDomainInfo *ad_info; + mlrpc_heapref_t heap; + nt_domain_t *nt_new_dp; + int opnum; + DWORD status; + + if (lsa_handle == 0) + return (NT_STATUS_INVALID_PARAMETER); + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_QueryInfoPolicy; + + bzero(&arg, sizeof (struct mslsa_QueryInfoPolicy)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + arg.info_class = infoClass; + + (void) mlsvc_rpc_init(&heap); + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + status = NT_SC_VALUE(arg.status); + } else { + switch (infoClass) { + case MSLSA_POLICY_PRIMARY_DOMAIN_INFO: + pd_info = &arg.info->ru.pd_info; + + nt_domain_flush(NT_DOMAIN_PRIMARY); + nt_new_dp = nt_domain_new(NT_DOMAIN_PRIMARY, + (char *)pd_info->name.str, + (nt_sid_t *)pd_info->sid); + (void) nt_domain_add(nt_new_dp); + status = NT_STATUS_SUCCESS; + break; + + case MSLSA_POLICY_ACCOUNT_DOMAIN_INFO: + ad_info = &arg.info->ru.ad_info; + + nt_domain_flush(NT_DOMAIN_ACCOUNT); + nt_new_dp = nt_domain_new(NT_DOMAIN_ACCOUNT, + (char *)ad_info->name.str, + (nt_sid_t *)ad_info->sid); + (void) nt_domain_add(nt_new_dp); + status = NT_STATUS_SUCCESS; + break; + + default: + status = NT_STATUS_INVALID_INFO_CLASS; + break; + } + } + + mlsvc_rpc_free(context, &heap); + return (status); +} + +/* + * lsar_lookup_names + * + * Lookup a name and obtain the domain and user rid. The RPC call will + * actually support lookup of multiple names but we probably don't + * need to do that. On the final system the lookup level should be + * level 2 but for now we want to restrict it to level 1 so that we + * don't crash the PDC when we get things wrong. + * + * If the lookup fails, the status will typically be + * NT_STATUS_NONE_MAPPED. + */ +int +lsar_lookup_names(mlsvc_handle_t *lsa_handle, char *name, + smb_userinfo_t *user_info) +{ + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + int index; + struct mslsa_LookupNames arg; + size_t length; + lookup_name_table_t name_table; + struct mslsa_rid_entry *rid_entry; + struct mslsa_domain_entry *domain_entry; + char *p; + + if (lsa_handle == NULL || name == NULL || user_info == NULL) + return (-1); + + bzero(user_info, sizeof (smb_userinfo_t)); + user_info->sid_name_use = SidTypeUnknown; + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_LookupNames; + + bzero(&arg, sizeof (struct mslsa_LookupNames)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + + arg.name_table = (struct mslsa_lup_name_table *)&name_table; + name_table.n_entry = 1; + + /* + * Windows NT expects the name length to exclude the terminating + * wchar null but doesn't care whether the allosize includes or + * excludes the null char. Windows 2000 insists that both the + * length and the allosize include the wchar null. + * + * Note: NT returns an error if the mapped_count is non-zero + * when the RPC is called. + */ + if (context->server_os == NATIVE_OS_WIN2000) { + /* + * Windows 2000 doesn't like an LSA lookup for + * DOMAIN\Administrator. + */ + if ((p = strchr(name, '\\')) != 0) { + ++p; + + if (strcasecmp(p, "administrator") == 0) + name = p; + } + + length = mts_wcequiv_strlen(name) + sizeof (mts_wchar_t); + arg.lookup_level = MSLSA_LOOKUP_LEVEL_1; + } else { + length = mts_wcequiv_strlen(name); + arg.lookup_level = MSLSA_LOOKUP_LEVEL_1; + } + + name_table.name[0].length = length; + name_table.name[0].allosize = length; + name_table.name[0].str = (unsigned char *)name; + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) { + rc = -1; + } else if (arg.mapped_count == 0) { + rc = -1; + } else { + rid_entry = &arg.translated_sids.rids[0]; + user_info->sid_name_use = rid_entry->sid_name_use; + user_info->rid = rid_entry->rid; + user_info->name = MEM_STRDUP("mlrpc", name); + + if ((index = rid_entry->domain_index) == -1) { + user_info->domain_sid = 0; + user_info->domain_name = 0; + } else { + domain_entry = + &arg.domain_table->entries[index]; + user_info->domain_sid = nt_sid_dup( + (nt_sid_t *)domain_entry->domain_sid); + user_info->domain_name = MEM_STRDUP("mlrpc", + (const char *) + domain_entry->domain_name.str); + } + } + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * lsar_lookup_sids + * + * Lookup a sid and obtain the domain sid and user name. The RPC call + * will actually support lookup of multiple sids but we probably don't + * need to do that. On the final system the lookup level should be + * level 2 but for now we want to restrict it to level 1 so that we + * don't crash the PDC when we get things wrong. + */ +int +lsar_lookup_sids(mlsvc_handle_t *lsa_handle, struct mslsa_sid *sid, + smb_userinfo_t *user_info) +{ + struct mslsa_LookupSids arg; + struct mslsa_lup_sid_entry sid_entry; + struct mslsa_name_entry *name_entry; + struct mslsa_domain_entry *domain_entry; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + int index; + + if (lsa_handle == NULL || sid == NULL || user_info == NULL) + return (-1); + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_LookupSids; + + bzero(&arg, sizeof (struct mslsa_LookupSids)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + arg.lookup_level = MSLSA_LOOKUP_LEVEL_2; + + sid_entry.psid = sid; + arg.lup_sid_table.n_entry = 1; + arg.lup_sid_table.entries = &sid_entry; + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.mapped_count == 0) { + user_info->sid_name_use = SidTypeInvalid; + rc = 1; + } else { + name_entry = &arg.name_table.entries[0]; + user_info->sid_name_use = name_entry->sid_name_use; + + if (user_info->sid_name_use == SidTypeUser || + user_info->sid_name_use == SidTypeGroup || + user_info->sid_name_use == SidTypeAlias) { + + user_info->rid = + sid->SubAuthority[sid->SubAuthCount - 1]; + + user_info->name = MEM_STRDUP("mlrpc", + (const char *)name_entry->name.str); + } + + if ((index = name_entry->domain_ix) == -1) { + user_info->domain_sid = 0; + user_info->domain_name = 0; + } else { + domain_entry = + &arg.domain_table->entries[index]; + + user_info->domain_sid = nt_sid_dup( + (nt_sid_t *)domain_entry->domain_sid); + + user_info->domain_name = MEM_STRDUP("mlrpc", + (const char *) + domain_entry->domain_name.str); + } + } + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * lsar_enum_accounts + * + * Enumerate the list of accounts (i.e. SIDs). Use the handle returned + * from lsa_open_policy2. The enum_context is used to support multiple + * calls to this enumeration function. It should be set to 0 on the + * first call. It will be updated by the domain controller and should + * simply be passed unchanged to subsequent calls until there are no + * more accounts. A warning status of 0x1A indicates that no more data + * is available. The list of accounts will be returned in accounts. + * This list is dynamically allocated using malloc, it should be freed + * by the caller when it is no longer required. + */ +int +lsar_enum_accounts(mlsvc_handle_t *lsa_handle, DWORD *enum_context, + struct mslsa_EnumAccountBuf *accounts) +{ + struct mslsa_EnumerateAccounts arg; + struct mslsa_AccountInfo *info; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + DWORD n_entries; + DWORD i; + int nbytes; + + if (lsa_handle == NULL || enum_context == NULL || accounts == NULL) + return (-1); + + accounts->entries_read = 0; + accounts->info = 0; + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_EnumerateAccounts; + + bzero(&arg, sizeof (struct mslsa_EnumerateAccounts)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + arg.enum_context = *enum_context; + arg.max_length = MLSVC_MAX_RESPONSE_LEN; + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) { + if ((arg.status & 0x00FFFFFF) == MLSVC_NO_MORE_DATA) { + *enum_context = arg.enum_context; + } else { + mlsvc_rpc_report_status(opnum, + (DWORD)arg.status); + rc = -1; + } + } else if (arg.enum_buf->entries_read != 0) { + n_entries = arg.enum_buf->entries_read; + nbytes = n_entries * sizeof (struct mslsa_AccountInfo); + + info = (struct mslsa_AccountInfo *)MEM_MALLOC("mlrpc", + nbytes); + if (info == NULL) { + mlsvc_rpc_free(context, &heap); + return (-1); + } + + for (i = 0; i < n_entries; ++i) + info[i].sid = (struct mslsa_sid *)nt_sid_dup( + (nt_sid_t *)arg.enum_buf->info[i].sid); + + accounts->entries_read = n_entries; + accounts->info = info; + *enum_context = arg.enum_context; + } + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * lsar_enum_trusted_domains + * + * Enumerate the list of trusted domains. Use the handle returned from + * lsa_open_policy2. The enum_context is used to support multiple calls + * to this enumeration function. It should be set to 0 on the first + * call. It will be updated by the domain controller and should simply + * be passed unchanged to subsequent calls until there are no more + * domains. + * + * The trusted domains aren't actually returned here. They are added + * to the NT domain database. After all of the trusted domains have + * been discovered, the database can be interrogated to find all of + * the trusted domains. + */ +DWORD +lsar_enum_trusted_domains(mlsvc_handle_t *lsa_handle, DWORD *enum_context) +{ + struct mslsa_EnumTrustedDomain arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + nt_domain_t *nt_new_dp; + int opnum; + DWORD status; + DWORD n_entries; + DWORD i; + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_EnumTrustedDomain; + + bzero(&arg, sizeof (struct mslsa_EnumTrustedDomain)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + arg.enum_context = *enum_context; + arg.max_length = MLSVC_MAX_RESPONSE_LEN; + + (void) mlsvc_rpc_init(&heap); + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.status != 0) { + *enum_context = arg.enum_context; + status = NT_SC_VALUE(arg.status); + + /* + * status 0x8000001A means NO_MORE_DATA, + * which is not an error. + */ + if (status != MLSVC_NO_MORE_DATA) + mlsvc_rpc_report_status(opnum, arg.status); + } else if (arg.enum_buf->entries_read == 0) { + *enum_context = arg.enum_context; + status = 0; + } else { + nt_domain_flush(NT_DOMAIN_TRUSTED); + n_entries = arg.enum_buf->entries_read; + + for (i = 0; i < n_entries; ++i) { + nt_new_dp = nt_domain_new( + NT_DOMAIN_TRUSTED, + (char *)arg.enum_buf->info[i].name.str, + (nt_sid_t *)arg.enum_buf->info[i].sid); + + (void) nt_domain_add(nt_new_dp); + } + + *enum_context = arg.enum_context; + status = 0; + } + + mlsvc_rpc_free(context, &heap); + return (status); +} + +/* + * lsar_enum_privs_account + * + * Privileges enum? Need an account handle. + */ +/*ARGSUSED*/ +int +lsar_enum_privs_account(mlsvc_handle_t *account_handle, + smb_userinfo_t *user_info) +{ + struct mslsa_EnumPrivsAccount arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + + context = account_handle->context; + opnum = LSARPC_OPNUM_EnumPrivsAccount; + + bzero(&arg, sizeof (struct mslsa_EnumPrivsAccount)); + (void) memcpy(&arg.account_handle, &account_handle->handle, + sizeof (mslsa_handle_t)); + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if ((rc == 0) && (arg.status != 0)) { + mlsvc_rpc_report_status(opnum, (DWORD)arg.status); + rc = -1; + } + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * lsar_lookup_priv_value + * + * Map a privilege name to a local unique id (LUID). Privilege names + * are consistent across the network. LUIDs are machine specific. + * This function provides the means to map a privilege name to the + * LUID used by a remote server to represent it. The handle here is + * a policy handle. + */ +int +lsar_lookup_priv_value(mlsvc_handle_t *lsa_handle, char *name, + struct ms_luid *luid) +{ + struct mslsa_LookupPrivValue arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + size_t length; + + if (lsa_handle == NULL || name == NULL || luid == NULL) + return (-1); + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_LookupPrivValue; + + bzero(&arg, sizeof (struct mslsa_LookupPrivValue)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + + length = mts_wcequiv_strlen(name); + if (context->server_os == NATIVE_OS_WIN2000) + length += sizeof (mts_wchar_t); + + arg.name.length = length; + arg.name.allosize = length; + arg.name.str = (unsigned char *)name; + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) + rc = -1; + else + (void) memcpy(luid, &arg.luid, sizeof (struct ms_luid)); + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * lsar_lookup_priv_name + * + * Map a local unique id (LUID) to a privilege name. Privilege names + * are consistent across the network. LUIDs are machine specific. + * This function the means to map the LUID used by a remote server to + * the appropriate privilege name. The handle here is a policy handle. + */ +int +lsar_lookup_priv_name(mlsvc_handle_t *lsa_handle, struct ms_luid *luid, + char *name, int namelen) +{ + struct mslsa_LookupPrivName arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + + if (lsa_handle == NULL || luid == NULL || name == NULL) + return (-1); + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_LookupPrivName; + + bzero(&arg, sizeof (struct mslsa_LookupPrivName)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + (void) memcpy(&arg.luid, luid, sizeof (struct ms_luid)); + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) + rc = -1; + else + (void) strlcpy(name, (char const *)arg.name->str, + namelen); + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * lsar_lookup_priv_display_name + * + * Map a privilege name to a privilege display name. The input handle + * should be an LSA policy handle and the name would normally be one + * of the privileges defined in smb_privilege.h + * + * There's something peculiar about the return status from NT servers, + * it's not always present. So for now, I'm ignoring the status in the + * RPC response. + * + * Returns NT status codes. + */ +DWORD +lsar_lookup_priv_display_name(mlsvc_handle_t *lsa_handle, char *name, + char *display_name, int display_len) +{ + struct mslsa_LookupPrivDisplayName arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + size_t length; + DWORD status; + + if (lsa_handle == NULL || name == NULL || display_name == NULL) + return (NT_STATUS_INVALID_PARAMETER); + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_LookupPrivDisplayName; + + bzero(&arg, sizeof (struct mslsa_LookupPrivDisplayName)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + + length = mts_wcequiv_strlen(name); + arg.name.length = length; + arg.name.allosize = length; + arg.name.str = (unsigned char *)name; + + arg.client_language = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); + arg.default_language = MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL); + + (void) mlsvc_rpc_init(&heap); + + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) + status = NT_STATUS_INVALID_PARAMETER; +#if 0 + else if (arg.status != 0) + status = NT_SC_VALUE(arg.status); +#endif + else { + (void) strlcpy(display_name, + (char const *)arg.display_name->str, display_len); + status = NT_STATUS_SUCCESS; + } + + mlsvc_rpc_free(context, &heap); + return (status); +} + +/* + * lsar_lookup_sids2 + */ +DWORD +lsar_lookup_sids2(mlsvc_handle_t *lsa_handle, struct mslsa_sid *sid, + smb_userinfo_t *user_info) +{ + struct lsar_lookup_sids2 arg; + struct lsar_name_entry2 *name_entry; + struct mslsa_lup_sid_entry sid_entry; + struct mslsa_domain_entry *domain_entry; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int index; + DWORD status; + + if (lsa_handle == NULL || sid == NULL || user_info == NULL) + return (NT_STATUS_INVALID_PARAMETER); + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_LookupSids2; + + if (context->server_os != NATIVE_OS_WIN2000) + return (NT_STATUS_REVISION_MISMATCH); + + bzero(&arg, sizeof (struct lsar_lookup_sids2)); + (void) memcpy(&arg.policy_handle, lsa_handle, sizeof (mslsa_handle_t)); + + sid_entry.psid = sid; + arg.lup_sid_table.n_entry = 1; + arg.lup_sid_table.entries = &sid_entry; + arg.lookup_level = MSLSA_LOOKUP_LEVEL_1; + arg.requested_count = arg.lup_sid_table.n_entry; + + (void) mlsvc_rpc_init(&heap); + + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.mapped_count == 0) { + user_info->sid_name_use = SidTypeInvalid; + status = NT_STATUS_NONE_MAPPED; + } else if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + status = NT_SC_VALUE(arg.status); + } else { + status = 0; + + name_entry = &arg.name_table.entries[0]; + user_info->sid_name_use = name_entry->sid_name_use; + + if (user_info->sid_name_use == SidTypeUser || + user_info->sid_name_use == SidTypeGroup || + user_info->sid_name_use == SidTypeAlias) { + + user_info->rid = + sid->SubAuthority[sid->SubAuthCount - 1]; + + user_info->name = MEM_STRDUP("mlrpc", + (char const *)name_entry->name.str); + + } + + if ((index = name_entry->domain_ix) == -1) { + user_info->domain_sid = 0; + user_info->domain_name = 0; + } else { + domain_entry = &arg.domain_table->entries[index]; + + user_info->domain_sid = nt_sid_dup( + (nt_sid_t *)domain_entry->domain_sid); + + user_info->domain_name = MEM_STRDUP("mlrpc", + (char const *)domain_entry->domain_name.str); + } + } + + mlsvc_rpc_free(context, &heap); + return (status); +} + + +/* + * lsar_lookup_names2 + * + * Windows NT expects the name length to exclude the terminating + * wchar null but Windows 2000 insists that both the length and + * the allosize include the wchar null. Windows NT doesn't care + * whether or not the allosize includes or excludes the null char. + * + * As a precaution, I set the lookup level to 1 on Windows 2000 + * until I can do some more testing. + * + * Note that NT returns an error if the mapped_count is non-zero + * when the RPC is called. + * + * It should be okay to lookup DOMAIN\Administrator in this function. + */ +DWORD +lsar_lookup_names2(mlsvc_handle_t *lsa_handle, char *name, + smb_userinfo_t *user_info) +{ + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int index; + struct lsar_LookupNames2 arg; + size_t length; + lookup_name_table_t name_table; + struct lsar_rid_entry2 *rid_entry; + struct mslsa_domain_entry *domain_entry; + DWORD status; + + if (lsa_handle == NULL || name == NULL || user_info == NULL) + return (NT_STATUS_INVALID_PARAMETER); + + bzero(user_info, sizeof (smb_userinfo_t)); + user_info->sid_name_use = SidTypeUnknown; + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_LookupNames2; + + if (context->server_os != NATIVE_OS_WIN2000) + return (NT_STATUS_REVISION_MISMATCH); + + bzero(&arg, sizeof (struct lsar_LookupNames2)); + (void) memcpy(&arg.policy_handle, lsa_handle, sizeof (mslsa_handle_t)); + arg.unknown_sb2 = 0x00000002; + arg.lookup_level = MSLSA_LOOKUP_LEVEL_1; + + arg.name_table = (struct mslsa_lup_name_table *)&name_table; + name_table.n_entry = 1; + + length = mts_wcequiv_strlen(name) + sizeof (mts_wchar_t); + name_table.name[0].length = length; + name_table.name[0].allosize = length; + name_table.name[0].str = (unsigned char *)name; + + (void) mlsvc_rpc_init(&heap); + + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + status = NT_SC_VALUE(arg.status); + } else if (arg.mapped_count == 0) { + user_info->sid_name_use = SidTypeInvalid; + status = NT_STATUS_NONE_MAPPED; + } else { + status = 0; + + rid_entry = &arg.translated_sids.rids[0]; + user_info->sid_name_use = rid_entry->sid_name_use; + user_info->rid = rid_entry->rid; + user_info->name = MEM_STRDUP("mlrpc", name); + + if ((index = rid_entry->domain_index) == -1) { + user_info->domain_sid = 0; + user_info->domain_name = 0; + } else { + domain_entry = &arg.domain_table->entries[index]; + + user_info->domain_sid = nt_sid_dup( + (nt_sid_t *)domain_entry->domain_sid); + + user_info->domain_name = MEM_STRDUP("mlrpc", + (char const *)domain_entry->domain_name.str); + } + } + + mlsvc_rpc_free(context, &heap); + return (status); +} + +void +mlsvc_rpc_report_status(int opnum, DWORD status) +{ + char *s = "unknown"; + + if (status == 0) + s = "success"; + else if (NT_SC_IS_ERROR(status)) + s = "error"; + else if (NT_SC_IS_WARNING(status)) + s = "warning"; + else if (NT_SC_IS_INFO(status)) + s = "info"; + + smb_tracef("mlrpc[0x%02x]: %s: %s (0x%08x)", + opnum, s, xlate_nt_status(status), status); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/lsar_open.c b/usr/src/lib/smbsrv/libmlsvc/common/lsar_open.c new file mode 100644 index 0000000000..57b1b62a97 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/lsar_open.c @@ -0,0 +1,298 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Local Security Authority RPC (LSARPC) library interface functions for + * open and close calls. + */ + +#include <stdio.h> +#include <strings.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libsmbrdr.h> +#include <smbsrv/mlsvc.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/ntaccess.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/lsalib.h> + +/* + * lsar_open + * + * This is a wrapper round lsar_open_policy2 to ensure that we connect + * using the appropriate session and logon. We default to the resource + * domain information if the caller didn't supply a server name and a + * domain name. + * + * On success 0 is returned. Otherwise a -ve error code. + */ +int lsar_open(int ipc_mode, char *server, char *domain, char *username, + char *password, mlsvc_handle_t *domain_handle) +{ + smb_ntdomain_t *di; + int remote_os; + int remote_lm; + int rc; + + if ((di = smb_getdomaininfo(0)) == NULL) + return (-1); + + if (server == NULL || domain == NULL) { + server = di->server; + domain = di->domain; + } + + switch (ipc_mode) { + case MLSVC_IPC_USER: + /* + * Use the supplied credentials. + */ + rc = mlsvc_user_logon(server, domain, username, password); + break; + + case MLSVC_IPC_ADMIN: + /* + * Use the resource domain administrator credentials. + */ + server = di->server; + domain = di->domain; + username = smbrdr_ipc_get_user(); + + rc = mlsvc_admin_logon(server, domain); + break; + + case MLSVC_IPC_ANON: + default: + rc = mlsvc_anonymous_logon(server, domain, &username); + break; + } + + if (rc != 0) + return (-1); + + rc = lsar_open_policy2(server, domain, username, domain_handle); + if (rc == 0) { + if (mlsvc_session_native_values(domain_handle->context->fid, + &remote_os, &remote_lm, 0) != 0) + remote_os = NATIVE_OS_UNKNOWN; + + domain_handle->context->server_os = remote_os; + } + return (rc); +} + + +/* + * lsar_open_policy2 + * + * Obtain an LSA policy handle. A policy handle is required to access + * LSA resources on a remote server. The server name supplied here does + * not need the double backslash prefix; it is added here. Call this + * function via lsar_open to ensure that the appropriate connection is + * in place. + * + * I'm not sure if it makes a difference whether we use GENERIC_EXECUTE + * or STANDARD_RIGHTS_EXECUTE. For a long time I used the standard bit + * and then I added the generic bit while working on privileges because + * NT sets that bit. I don't think it matters. + * + * Returns 0 on success. Otherwise non-zero to indicate a failure. + */ +int lsar_open_policy2(char *server, char *domain, char *username, + mlsvc_handle_t *lsa_handle) +{ + struct mslsa_OpenPolicy2 arg; + mlrpc_heapref_t heap; + int rc; + int opnum; + int fid; + int remote_os; + int remote_lm; + int len; + + if (server == NULL || domain == NULL || + username == NULL || lsa_handle == NULL) + return (-1); + + fid = mlsvc_open_pipe(server, domain, username, "\\lsarpc"); + if (fid < 0) + return (-1); + + if ((rc = mlsvc_rpc_bind(lsa_handle, fid, "LSARPC")) < 0) { + (void) mlsvc_close_pipe(fid); + return (rc); + } + + opnum = LSARPC_OPNUM_OpenPolicy2; + bzero(&arg, sizeof (struct mslsa_OpenPolicy2)); + + len = strlen(server) + 4; + arg.servername = malloc(len); + if (arg.servername == NULL) { + (void) mlsvc_close_pipe(fid); + free(lsa_handle->context); + return (-1); + } + + (void) snprintf((char *)arg.servername, len, "\\\\%s", server); + arg.attributes.length = sizeof (struct mslsa_object_attributes); + + (void) mlsvc_session_native_values(fid, &remote_os, &remote_lm, 0); + + if (remote_os == NATIVE_OS_NT5_0) { + arg.desiredAccess = MAXIMUM_ALLOWED; + } else { + arg.desiredAccess = GENERIC_EXECUTE + | STANDARD_RIGHTS_EXECUTE + | POLICY_VIEW_LOCAL_INFORMATION + | POLICY_LOOKUP_NAMES; + } + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(lsa_handle->context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) { + rc = -1; + } else { + (void) memcpy(&lsa_handle->handle, &arg.domain_handle, + sizeof (mslsa_handle_t)); + + if (mlsvc_is_null_handle(lsa_handle)) + rc = -1; + } + } + + mlsvc_rpc_free(lsa_handle->context, &heap); + free(arg.servername); + + if (rc != 0) { + (void) mlsvc_close_pipe(fid); + free(lsa_handle->context); + } + + return (rc); +} + +/* + * lsar_open_account + * + * Obtain an LSA account handle. The lsa_handle must be a valid handle + * obtained via lsar_open_policy2. The main thing to remember here is + * to set up the context in the lsa_account_handle. I'm not sure what + * the requirements are for desired access. Some values require admin + * access. + * + * Returns 0 on success. Otherwise non-zero to indicate a failure. + */ +int +lsar_open_account(mlsvc_handle_t *lsa_handle, struct mslsa_sid *sid, + mlsvc_handle_t *lsa_account_handle) +{ + struct mslsa_OpenAccount arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int rc; + int opnum; + + if (mlsvc_is_null_handle(lsa_handle) || + sid == NULL || lsa_account_handle == NULL) + return (-1); + + context = lsa_handle->context; + opnum = LSARPC_OPNUM_OpenAccount; + bzero(&arg, sizeof (struct mslsa_OpenAccount)); + + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + arg.sid = sid; + arg.access_mask = STANDARD_RIGHTS_REQUIRED +#if 0 + | POLICY_VIEW_AUDIT_INFORMATION + | POLICY_GET_PRIVATE_INFORMATION + | POLICY_TRUST_ADMIN +#endif + | POLICY_VIEW_LOCAL_INFORMATION; + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) { + rc = -1; + } else { + lsa_account_handle->context = context; + + (void) memcpy(&lsa_account_handle->handle, + &arg.account_handle, sizeof (mslsa_handle_t)); + + if (mlsvc_is_null_handle(lsa_account_handle)) + rc = -1; + } + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * lsar_close + * + * Close the LSA connection associated with the handle. The lsa_handle + * must be a valid handle obtained via a call to lsar_open_policy2 or + * lsar_open_account. On success the handle will be zeroed out to + * ensure that it is not used again. If this is the top level handle + * (i.e. the one obtained via lsar_open_policy2) the pipe is closed + * and the context is freed. + * + * Returns 0 on success. Otherwise non-zero to indicate a failure. + */ +int +lsar_close(mlsvc_handle_t *lsa_handle) +{ + struct mslsa_CloseHandle arg; + mlrpc_heapref_t heap; + int rc; + int opnum; + + if (mlsvc_is_null_handle(lsa_handle)) + return (-1); + + opnum = LSARPC_OPNUM_CloseHandle; + bzero(&arg, sizeof (struct mslsa_CloseHandle)); + (void) memcpy(&arg.handle, lsa_handle, sizeof (mslsa_handle_t)); + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(lsa_handle->context, opnum, &arg, &heap); + mlsvc_rpc_free(lsa_handle->context, &heap); + + if (lsa_handle->context->handle == &lsa_handle->handle) { + (void) mlsvc_close_pipe(lsa_handle->context->fid); + free(lsa_handle->context); + } + + bzero(lsa_handle, sizeof (mlsvc_handle_t)); + return (rc); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mapfile-vers b/usr/src/lib/smbsrv/libmlsvc/common/mapfile-vers new file mode 100644 index 0000000000..e05f51b279 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mapfile-vers @@ -0,0 +1,97 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# ident "%Z%%M% %I% %E% SMI" + +SUNWprivate { + global: + lmshare_add; + lmshare_close_iterator; + lmshare_delete; + lmshare_exists; + lmshare_getinfo; + lmshare_get_realpath; + lmshare_is_admin; + lmshare_is_dir; + lmshare_is_restricted; + lmshare_is_special; + lmshare_is_valid; + lmshare_iterate; + lmshare_list; + lmshare_num_shares; + lmshare_open_iterator; + lmshare_rename; + lmshare_setinfo; + lmshare_start; + lmshare_stop; + lsa_lookup_name2; + lsa_lookup_sid2; + lsa_query_primary_domain_info; + lsa_query_account_domain_info; + lsa_enum_trusted_domains; + mlsvc_init; + mlsvc_validate_user; + mlsvc_is_local_domain; + nt_group_add; + nt_group_add_groupprivs; + nt_group_add_member_byname; + nt_group_cache_size; + nt_group_close_iterator; + nt_group_delete; + nt_group_del_member_byname; + nt_group_getinfo; + nt_group_getpriv; + nt_group_ht_lock; + nt_group_ht_unlock; + nt_group_is_member; + nt_group_iterate; + nt_group_modify; + nt_group_num_groups; + nt_group_num_members; + nt_group_open_iterator; + nt_group_putinfo; + nt_groups_count; + nt_group_setpriv; + nt_groups_lookup_rid; + nt_groups_member_groups; + nt_groups_member_ngroups; + nt_groups_member_privs; + nt_group_list; + nt_group_member_list; + sam_init; + smb_logon; + smb_token_destroy; + smb_build_lmshare_info; + smb_get_smb_share_group; + smb_autohome_add; + smb_autohome_endent; + smb_autohome_getent; + smb_autohome_lookup; + smb_autohome_remove; + smb_autohome_setent; + smb_is_autohome; + local: + *; +}; diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_client.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_client.c new file mode 100644 index 0000000000..ac8cc3003c --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_client.c @@ -0,0 +1,306 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Context functions to support the RPC interface library. + */ + +#include <sys/errno.h> +#include <strings.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libsmbrdr.h> +#include <smbsrv/mlsvc_util.h> + +static int mlsvc_xa_init(struct mlrpc_client *, struct mlrpc_xaction *, + mlrpc_heap_t *); +static int mlsvc_xa_exchange(struct mlrpc_client *, struct mlrpc_xaction *); +static int mlsvc_xa_read(struct mlrpc_client *, struct mlrpc_xaction *); +static int mlsvc_xa_preserve(struct mlrpc_client *, struct mlrpc_xaction *, + mlrpc_heapref_t *); +static int mlsvc_xa_destruct(struct mlrpc_client *, struct mlrpc_xaction *); +static void mlsvc_xa_release(struct mlrpc_client *, mlrpc_heapref_t *heapref); + +/* + * mlsvc_rpc_bind + * + * This the entry point for all client RPC services. This call must be + * made to initialize an RPC context structure and bind to the remote + * service before any RPCs can be exchanged with that service. The + * descriptor is a wrapper that is used to associate an RPC handle with + * the context data for that specific instance of the interface. The + * handle is zeroed to ensure that it doesn't look like a valid handle. + * The context handle is assigned to point at the RPC handle so that we + * know when to free the context. As each handle is initialized it will + * include a pointer to this context but only when we close this initial + * RPC handle can the context be freed. + * + * On success, return a pointer to the descriptor. Otherwise return a + * null pointer. + */ +int +mlsvc_rpc_bind(mlsvc_handle_t *desc, int fid, char *service) +{ + struct mlsvc_rpc_context *context; + int rc; + + bzero(&desc->handle, sizeof (ms_handle_t)); + + context = malloc(sizeof (struct mlsvc_rpc_context)); + if ((desc->context = context) == NULL) + return (-1); + + bzero(context, sizeof (struct mlsvc_rpc_context)); + context->cli.context = context; + + mlrpc_binding_pool_initialize(&context->cli.binding_list, + context->binding_pool, CTXT_N_BINDING_POOL); + + context->fid = fid; + context->handle = &desc->handle; + context->cli.xa_init = mlsvc_xa_init; + context->cli.xa_exchange = mlsvc_xa_exchange; + context->cli.xa_read = mlsvc_xa_read; + context->cli.xa_preserve = mlsvc_xa_preserve; + context->cli.xa_destruct = mlsvc_xa_destruct; + context->cli.xa_release = mlsvc_xa_release; + + rc = mlrpc_c_bind(&context->cli, service, &context->binding); + if (MLRPC_DRC_IS_FAULT(rc)) { + free(context); + desc->context = NULL; + return (-1); + } + + return (rc); +} + +/* + * mlsvc_rpc_init + * + * This function must be called by client side applications before + * calling mlsvc_rpc_call to allocate a heap. The heap must be + * destroyed by either calling mlrpc_heap_destroy or mlsvc_rpc_free. + * Use mlrpc_heap_destroy if mlsvc_rpc_call has not yet been called. + * Otherwise use mlsvc_rpc_free. + * + * Returns 0 on success. Otherwise returns -1 to indicate an error. + */ +int +mlsvc_rpc_init(mlrpc_heapref_t *heapref) +{ + bzero(heapref, sizeof (mlrpc_heapref_t)); + + if ((heapref->heap = mlrpc_heap_create()) == NULL) + return (-1); + + return (0); +} + +/* + * mlsvc_rpc_call + * + * This function should be called by the client RPC interface functions + * to make an RPC call. The remote service is identified by the context + * handle, which should have been initialized with by mlsvc_rpc_bind. + */ +int +mlsvc_rpc_call(struct mlsvc_rpc_context *context, int opnum, void *params, + mlrpc_heapref_t *heapref) +{ + return (mlrpc_c_call(context->binding, opnum, params, heapref)); +} + +/* + * mlsvc_rpc_free + * + * This function should be called by the client RPC interface functions + * to free the heap after an RPC call returns. + */ +void +mlsvc_rpc_free(struct mlsvc_rpc_context *context, mlrpc_heapref_t *heapref) +{ + mlrpc_c_free_heap(context->binding, heapref); +} + +/* + * The following functions provide the callback interface in the + * context handle. + */ +/*ARGSUSED*/ +static int +mlsvc_xa_init(struct mlrpc_client *mcli, struct mlrpc_xaction *mxa, + mlrpc_heap_t *heap) +{ + struct mlndr_stream *recv_mlnds = &mxa->recv_mlnds; + struct mlndr_stream *send_mlnds = &mxa->send_mlnds; + + /* + * If the caller hasn't provided a heap, create one here. + */ + if (heap == 0) { + if ((heap = mlrpc_heap_create()) == 0) + return (-1); + } + + mxa->heap = heap; + + mlnds_initialize(send_mlnds, 0, NDR_MODE_CALL_SEND, heap); + mlnds_initialize(recv_mlnds, 16 * 1024, NDR_MODE_RETURN_RECV, heap); + return (0); +} + +/* + * mlsvc_xa_exchange + * + * This is the entry pointy for an RPC client call exchange with + * a server, which will result in an smbrdr SmbTransact request. + * + * SmbTransact should return the number of bytes received, which + * we record as the PDU size, or a negative error code. + */ +static int +mlsvc_xa_exchange(struct mlrpc_client *mcli, struct mlrpc_xaction *mxa) +{ + struct mlsvc_rpc_context *context = mcli->context; + struct mlndr_stream *recv_mlnds = &mxa->recv_mlnds; + struct mlndr_stream *send_mlnds = &mxa->send_mlnds; + int rc; + + rc = smbrdr_rpc_transact(context->fid, + (char *)send_mlnds->pdu_base_offset, send_mlnds->pdu_size, + (char *)recv_mlnds->pdu_base_offset, recv_mlnds->pdu_max_size); + + if (rc < 0) + recv_mlnds->pdu_size = 0; + else + recv_mlnds->pdu_size = rc; + + return (rc); +} + +/* + * mlsvc_xa_read + * + * This entry point will be invoked if the xa-exchange response contained + * only the first fragment of a multi-fragment response. The RPC client + * code will then make repeated xa-read requests to obtain the remaining + * fragments, which will result in smbrdr SmbReadX requests. + * + * SmbReadX should return the number of bytes received, in which case we + * expand the PDU size to include the received data, or a negative error + * code. + */ +static int +mlsvc_xa_read(struct mlrpc_client *mcli, struct mlrpc_xaction *mxa) +{ + struct mlsvc_rpc_context *context = mcli->context; + struct mlndr_stream *mlnds = &mxa->recv_mlnds; + int len; + int rc; + + if ((len = (mlnds->pdu_max_size - mlnds->pdu_size)) < 0) + return (-1); + + rc = smbrdr_rpc_readx(context->fid, + (char *)mlnds->pdu_base_offset + mlnds->pdu_size, len); + + if (rc < 0) + return (-1); + + mlnds->pdu_size += rc; + + if (mlnds->pdu_size > mlnds->pdu_max_size) { + mlnds->pdu_size = mlnds->pdu_max_size; + return (-1); + } + + return (rc); +} + +/* + * mlsvc_xa_preserve + * + * This function is called to preserve the heap. We save a reference + * to the heap and set the mxa heap pointer to null so that the heap + * will not be discarded when mlsvc_xa_destruct is called. + */ +/*ARGSUSED*/ +static int +mlsvc_xa_preserve(struct mlrpc_client *mcli, struct mlrpc_xaction *mxa, + mlrpc_heapref_t *heapref) +{ + heapref->state = MLRPC_HRST_PRESERVED; + heapref->heap = mxa->heap; + heapref->recv_pdu_buf = (char *)mxa->recv_mlnds.pdu_base_addr; + heapref->send_pdu_buf = (char *)mxa->send_mlnds.pdu_base_addr; + + mxa->heap = NULL; + return (0); +} + +/* + * mlsvc_xa_destruct + * + * This function is called to dispose of the heap. If the heap has + * been preserved via mlsvc_xa_preserve, the mxa heap pointer will + * be null and we assume that the heap will be released later via + * a call to mlsvc_xa_release. Otherwise we free the memory here. + */ +/*ARGSUSED*/ +static int +mlsvc_xa_destruct(struct mlrpc_client *mcli, struct mlrpc_xaction *mxa) +{ + if (mxa->heap) { + mlnds_destruct(&mxa->recv_mlnds); + mlnds_destruct(&mxa->send_mlnds); + mlrpc_heap_destroy(mxa->heap); + } + + return (0); +} + +/* + * mlsvc_xa_release + * + * This function is called, via some indirection, as a result of a + * call to mlsvc_rpc_free. This is where we free the heap memory + * that was preserved during an RPC call. + */ +/*ARGSUSED*/ +static void +mlsvc_xa_release(struct mlrpc_client *mcli, mlrpc_heapref_t *heapref) +{ + if (heapref == NULL) + return; + + if (heapref->state == MLRPC_HRST_PRESERVED) { + free(heapref->recv_pdu_buf); + free(heapref->send_pdu_buf); + mlrpc_heap_destroy(heapref->heap); + } +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_dssetup.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_dssetup.c new file mode 100644 index 0000000000..a20304afa8 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_dssetup.c @@ -0,0 +1,168 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Active Directory Setup RPC interface used by Windows2000. + */ + +#include <strings.h> +#include <stdlib.h> +#include <netdb.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libmlrpc.h> +#include <smbsrv/libmlsvc.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/dssetup.ndl> +#include <smbsrv/ntstatus.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/nmpipes.h> + +static int dssetup_DsRoleGetPrimaryDomainInfo(void *, struct mlrpc_xaction *); + +static mlrpc_stub_table_t dssetup_stub_table[] = { + { dssetup_DsRoleGetPrimaryDomainInfo, + DSSETUP_OPNUM_DsRoleGetPrimaryDomainInfo }, + {0} +}; + +static mlrpc_service_t dssetup_service = { + "DSSETUP", /* name */ + "Active Directory Setup", /* desc */ + "\\lsarpc", /* endpoint */ + PIPE_LSASS, /* sec_addr_port */ + "3919286a-b10c-11d0-9ba800c04fd92ef5", 0, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(dssetup_interface), /* interface ti */ + dssetup_stub_table /* stub_table */ +}; + +/* + * dssetup_initialize + * + * This function registers the DSSETUP interface with the RPC runtime + * library. It must be called in order to use either the client side + * or the server side functions. + */ +void +dssetup_initialize(void) +{ + (void) mlrpc_register_service(&dssetup_service); +} + +/* + * Request for primary domain information and status. + */ +static int +dssetup_DsRoleGetPrimaryDomainInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct dssetup_DsRoleGetPrimaryDomainInfo *param = arg; + char dns_domain[MAXHOSTNAMELEN]; + smb_ntdomain_t *di; + DWORD status; + + switch (param->level) { + case DS_ROLE_BASIC_INFORMATION: + break; + + case DS_ROLE_UPGRADE_STATUS: + case DS_ROLE_OP_STATUS: + default: + bzero(param, + sizeof (struct dssetup_DsRoleGetPrimaryDomainInfo)); + param->status = NT_SC_ERROR(NT_STATUS_INVALID_LEVEL); + return (MLRPC_DRC_OK); + } + + di = smb_getdomaininfo(0); + (void) smb_getdomainname(dns_domain, MAXHOSTNAMELEN); + + if (di == NULL) { + bzero(param, + sizeof (struct dssetup_DsRoleGetPrimaryDomainInfo)); + param->status = NT_SC_ERROR(NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + return (MLRPC_DRC_OK); + } + + (void) utf8_strlwr(dns_domain); + + param->ru.info1.role = DS_ROLE_MEMBER_SERVER; + param->ru.info1.flags = 0; + param->ru.info1.nt_domain = + (uint8_t *)MLRPC_HEAP_STRSAVE(mxa, di->domain); + param->ru.info1.dns_domain = + (uint8_t *)MLRPC_HEAP_STRSAVE(mxa, dns_domain); + param->ru.info1.forest = + (uint8_t *)MLRPC_HEAP_STRSAVE(mxa, dns_domain); + bzero(¶m->ru.info1.domain_guid, sizeof (mlrpc_uuid_t)); + + if (param->ru.info1.nt_domain == NULL || + param->ru.info1.dns_domain == NULL || + param->ru.info1.forest == NULL) { + bzero(param, + sizeof (struct dssetup_DsRoleGetPrimaryDomainInfo)); + status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + } else { + status = NT_STATUS_SUCCESS; + } + + param->status = status; + return (MLRPC_DRC_OK); +} + +DECL_FIXUP_STRUCT(dssetup_GetPrimaryDomainInfo_ru); +DECL_FIXUP_STRUCT(dssetup_GetPrimaryDomainInfoRes); +DECL_FIXUP_STRUCT(dssetup_DsRoleGetPrimaryDomainInfo); + +void +fixup_dssetup_DsRoleGetPrimaryDomainInfo( + struct dssetup_DsRoleGetPrimaryDomainInfo *val) +{ + unsigned short size1 = 0; + unsigned short size2 = 0; + unsigned short size3 = 0; + + switch (val->switch_value) { + CASE_INFO_ENT(dssetup_DsRolePrimaryDomInfo, 1); + CASE_INFO_ENT(dssetup_DsRolePrimaryDomInfo, 2); + CASE_INFO_ENT(dssetup_DsRolePrimaryDomInfo, 3); + + default: + return; + }; + + size2 = size1 + (2 * sizeof (DWORD)); + size3 = size2 + sizeof (mlrpcconn_request_hdr_t) + sizeof (DWORD); + + FIXUP_PDU_SIZE(dssetup_GetPrimaryDomainInfo_ru, size1); + FIXUP_PDU_SIZE(dssetup_GetPrimaryDomainInfoRes, size2); + FIXUP_PDU_SIZE(dssetup_DsRoleGetPrimaryDomainInfo, size3); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_handle.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_handle.c new file mode 100644 index 0000000000..a13b7346f5 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_handle.c @@ -0,0 +1,179 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module provides the handles used in the various server-side + * RPC functions. I don't think other systems care about the value in + * the handle. It should be treated as an opaque data block. Handles + * are issued when a service is opened and obsoleted when it is closed. + * We should check incoming RPC requests to ensure that the handle + * being used is associated with the particular service being accessed. + */ + +#include <strings.h> +#include <unistd.h> +#include <assert.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ntsid.h> + +/* + * Each time a handle is allocated it is added to the global handle + * descriptor list because we need some way of identifying the + * interface and domain to which the handle was assigned when it is + * returned on a subsequent RPC. + */ +ms_handle_t mlsvc_handle; +ms_handle_desc_t *mlsvc_desc_list; + +/* + * mlsvc_get_handle + * + * This function returns a handle for use with the server-side RPC + * functions. Every time it is called it will increment the handle + * value and return a pointer to it. On NT, handle[0] always seems + * to be zero and handle[1] increments. The rest seems to be some + * sort of unique value so the local domain SID should do. + * + * The handle is added to the global handle descriptor list with the + * designated ifspec and key tag. + */ +ms_handle_t * +mlsvc_get_handle(ms_ifspec_t ifspec, char *key, DWORD discrim) +{ + ms_handle_desc_t *desc; + nt_sid_t *sid; + + if ((desc = malloc(sizeof (ms_handle_desc_t))) == NULL) + assert(desc); + + sid = nt_domain_local_sid(); + if (mlsvc_handle.handle[1] == 0) { + mlsvc_handle.handle[0] = 0; + mlsvc_handle.handle[1] = 0; + mlsvc_handle.handle[2] = sid->SubAuthority[1]; + mlsvc_handle.handle[3] = sid->SubAuthority[2]; + mlsvc_handle.handle[4] = sid->SubAuthority[3]; + } + + ++mlsvc_handle.handle[1]; + + bcopy(&mlsvc_handle, &desc->handle, sizeof (ms_handle_t)); + desc->ifspec = ifspec; + desc->discrim = discrim; + desc->next = mlsvc_desc_list; + mlsvc_desc_list = desc; + + if (key) + (void) strlcpy(desc->key, key, MLSVC_HANDLE_KEY_MAX); + else + desc->key[0] = '\0'; + + return (&mlsvc_handle); +} + + +/* + * mlsvc_put_handle + * + * Remove a handle from the global handle descriptor list and free the + * memory it was using. If the list contained the descriptor, a value + * of 0 is returned. Otherwise -1 is returned. + */ +int +mlsvc_put_handle(ms_handle_t *handle) +{ + ms_handle_desc_t *desc; + ms_handle_desc_t **ppdesc = &mlsvc_desc_list; + + assert(handle); + + while (*ppdesc) { + desc = *ppdesc; + + if (bcmp(&desc->handle, handle, sizeof (ms_handle_t)) == 0) { + *ppdesc = desc->next; + free(desc); + return (0); + } + + ppdesc = &(*ppdesc)->next; + } + + return (-1); +} + + +/* + * mlsvc_validate_handle + * + * Lookup a handle in the global handle descriptor list. If the handle + * is in the list, a pointer to the descriptor is returned. Otherwise + * a null pointer is returned. + */ +int +mlsvc_validate_handle(ms_handle_t *handle, char *key) +{ + ms_handle_desc_t *desc; + + assert(handle); + assert(key); + + if ((desc = mlsvc_lookup_handle(handle)) == 0) + return (NULL); + + if (strcmp(desc->key, key)) + return (NULL); + + return (1); +} + + +/* + * mlsvc_lookup_handle + * + * Lookup a handle in the global handle descriptor list. If the handle + * is in the list, a pointer to the descriptor is returned. Otherwise + * a null pointer is returned. + */ +ms_handle_desc_t * +mlsvc_lookup_handle(ms_handle_t *handle) +{ + ms_handle_desc_t *desc = mlsvc_desc_list; + + assert(handle); + + while (desc) { + if (bcmp(&desc->handle, handle, sizeof (ms_handle_t)) == 0) + return (desc); + + desc = desc->next; + } + + return (NULL); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_init.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_init.c new file mode 100644 index 0000000000..fc17554949 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_init.c @@ -0,0 +1,65 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <unistd.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libmlsvc.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/lsalib.h> + +void dssetup_initialize(void); +void srvsvc_initialize(void); +void wkssvc_initialize(void); +void lsarpc_initialize(void); +void logr_initialize(void); +void netr_initialize(void); +void samr_initialize(void); +void svcctl_initialize(void); +void winreg_initialize(void); + +/* + * All mlrpc initialization is invoked from here. + * Returns 0 upon success. Otherwise, returns -1. + */ +int +mlsvc_init(void) +{ + srvsvc_initialize(); + wkssvc_initialize(); + lsarpc_initialize(); + netr_initialize(); + dssetup_initialize(); + samr_initialize(); + svcctl_initialize(); + winreg_initialize(); + logr_initialize(); + + (void) sam_init(); + return (0); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_logr.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_logr.c new file mode 100644 index 0000000000..14c13a0315 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_logr.c @@ -0,0 +1,574 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Event Log Service RPC (LOGR) interface definition. + */ + +#include <sys/utsname.h> +#include <unistd.h> +#include <strings.h> +#include <netdb.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/nmpipes.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/eventlog.ndl> + +#define FWD +1 +#define REW -1 + +/* define the logging structs here - from syslog.h */ +#define NMSGMASK 1023 +#define MAXMSGLEN 223 +#define LOGBUFSIZE (MAXMSGLEN + 1) + +#define LOG_PRI(p) ((p) & LOG_PRIMASK) + +typedef struct log_entry { + struct timeval timestamp; /* time of log entry */ + int pri; /* message priority */ + char msg[LOGBUFSIZE]; /* log message text */ + int thread_id; /* calling function thread ID */ + char thread_name[12]; /* calling function thread name */ + unsigned long caller_adr; /* calling function address */ +} log_entry_t; + +typedef struct log_info { + log_entry_t entry[NMSGMASK+1]; + int ix; + int alarm_on; + int alarm_disable; + int timestamp_level; + int disp_msg_len; + int prefix_len; +} log_info_t; + +/* + * READ flags for EventLogRead + * + * EVENTLOG_SEEK_READ + * The read operation proceeds from the record specified by the + * dwRecordOffset parameter. This flag cannot be used with + * EVENTLOG_SEQUENTIAL_READ. + * + * EVENTLOG_SEQUENTIAL_READ + * The read operation proceeds sequentially from the last call to the + * ReadEventLog function using this handle. This flag cannot be used + * with EVENTLOG_SEEK_READ. + * + * If the buffer is large enough, more than one record can be read at + * the specified seek position; you must specify one of the following + * flags to indicate the direction for successive read operations. + * + * EVENTLOG_FORWARDS_READ + * The log is read in chronological order. This flag cannot be used + * with EVENTLOG_BACKWARDS_READ. + * + * EVENTLOG_BACKWARDS_READ + * The log is read in reverse chronological order. This flag cannot be + * used with EVENTLOG_FORWARDS_READ. + */ +#define EVENTLOG_SEQUENTIAL_READ 0x0001 +#define EVENTLOG_SEEK_READ 0x0002 +#define EVENTLOG_FORWARDS_READ 0x0004 +#define EVENTLOG_BACKWARDS_READ 0x0008 + +/* + * The types of events that can be logged. + */ +#define EVENTLOG_SUCCESS 0x0000 +#define EVENTLOG_ERROR_TYPE 0x0001 +#define EVENTLOG_WARNING_TYPE 0x0002 +#define EVENTLOG_INFORMATION_TYPE 0x0004 +#define EVENTLOG_AUDIT_SUCCESS 0x0008 +#define EVENTLOG_AUDIT_FAILURE 0x0010 + +/* + * Event Identifiers + * + * Event identifiers uniquely identify a particular event. Each event + * source can define its own numbered events and the description strings + * to which they are mapped. Event viewers can present these strings to + * the user. They should help the user understand what went wrong and + * suggest what actions to take. Direct the description at users solving + * their own problems, not at administrators or support technicians. + * Make the description clear and concise and avoid culture-specific + * phrases. + * + * The following diagram illustrates the format of an event identifier. + * + * 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +---+-+-+-----------------------+-------------------------------+ + * |Sev|C|R| Facility | Code | + * +---+-+-+-----------------------+-------------------------------+ + * + * Sev + * Indicates the severity. This is one of the following values: + * 00 - Success + * 01 - Informational + * 10 - Warning + * 11 - Error + * + * C + * Indicates a customer code (1) or a system code (0). + * R + * Reserved bit. + * Facility + * Facility code. + * Code + * Status code for the facility. + */ +#define EVENTID_SEVERITY_SUCCESS 0x00000000 +#define EVENTID_SEVERITY_INFO 0x40000000 +#define EVENTID_SEVERITY_WARNING 0x80000000 +#define EVENTID_SEVERITY_ERROR 0xC0000000 + +#define EVENTID_SYSTEM_CODE 0x00000000 +#define EVENTID_CUSTOMER_CODE 0x20000000 + +#define MAX_SRCNAME_LEN 20 +#define LOGR_KEY "LogrOpen" + + +static int logr_s_EventLogClose(void *, struct mlrpc_xaction *); +static int logr_s_EventLogQueryCount(void *, struct mlrpc_xaction *); +static int logr_s_EventLogGetOldestRec(void *, struct mlrpc_xaction *); +static int logr_s_EventLogOpen(void *, struct mlrpc_xaction *); +static int logr_s_EventLogRead(void *, struct mlrpc_xaction *); + +static mlrpc_stub_table_t logr_stub_table[] = { + { logr_s_EventLogClose, LOGR_OPNUM_EventLogClose }, + { logr_s_EventLogQueryCount, LOGR_OPNUM_EventLogQueryCount }, + { logr_s_EventLogGetOldestRec, LOGR_OPNUM_EventLogGetOldestRec }, + { logr_s_EventLogOpen, LOGR_OPNUM_EventLogOpen }, + { logr_s_EventLogRead, LOGR_OPNUM_EventLogRead }, + {0} +}; + +static mlrpc_service_t logr_service = { + "LOGR", /* name */ + "Event Log Service", /* desc */ + "\\eventlog", /* endpoint */ + PIPE_NTSVCS, /* sec_addr_port */ + "82273fdc-e32a-18c3-3f78827929dc23ea", 0, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(logr_interface), /* interface ti */ + logr_stub_table /* stub_table */ +}; + +typedef struct { + DWORD tot_recnum; + DWORD last_sentrec; + char first_read; + struct log_info log; +} read_data_t; + +static char logr_sysname[SYS_NMLN]; +static char hostname[MAXHOSTNAMELEN]; +static mts_wchar_t wcs_hostname[MAXHOSTNAMELEN]; +static int hostname_len = 0; +static mts_wchar_t wcs_srcname[MAX_SRCNAME_LEN]; +static int srcname_len = 0; +static int str_offs, sh_len; + +/* + * logr_initialize + * + * This function registers the LOGR RPC interface with the RPC runtime + * library. It must be called in order to use either the client side + * or the server side functions. + */ +void +logr_initialize(void) +{ + struct utsname name; + char *sysname; + + if (uname(&name) < 0) + sysname = "Solaris"; + else + sysname = name.sysname; + + (void) strlcpy(logr_sysname, sysname, SYS_NMLN); + (void) mlrpc_register_service(&logr_service); +} + +/* + * logr_s_EventLogClose + * + * This is a request to close the LOGR interface specified by the + * handle. Free the handle and its associated resources and zero out + * the result handle for the client. + */ +/*ARGSUSED*/ +static int +logr_s_EventLogClose(void *arg, struct mlrpc_xaction *mxa) +{ + struct logr_EventLogClose *param = arg; + ms_handle_desc_t *desc; + read_data_t *data; + + if ((desc = mlsvc_lookup_handle((ms_handle_t *)¶m->handle)) == 0) { + param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE); + return (MLRPC_DRC_OK); + } + + data = (read_data_t *)(desc->discrim); + free(data); + (void) mlsvc_put_handle((ms_handle_t *)¶m->handle); + + bzero(¶m->result_handle, sizeof (logr_handle_t)); + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * logr_s_EventLogOpen + * + * This is a request to open the event log. + * + * Return a handle for use with subsequent event log requests. + */ +/*ARGSUSED*/ +static int +logr_s_EventLogOpen(void *arg, struct mlrpc_xaction *mxa) +{ + struct logr_EventLogOpen *param = arg; + ms_handle_t *handle; + int log_enable = 0; + + smb_config_rdlock(); + log_enable = smb_config_getyorn(SMB_CI_LOGR_ENABLE); + smb_config_unlock(); + + if (log_enable == 0) { + bzero(¶m->handle, sizeof (logr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + } + + handle = mlsvc_get_handle(MLSVC_IFSPEC_LOGR, LOGR_KEY, 0); + bcopy(handle, ¶m->handle, sizeof (logr_handle_t)); + + if (hostname_len == 0) { + if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0) { + bzero(¶m->handle, sizeof (logr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + } + + hostname_len = (strlen(hostname) + 1) * 2; + (void) mts_mbstowcs(wcs_hostname, hostname, hostname_len / 2); + srcname_len = (strlen(logr_sysname) + 1) * 2; + (void) mts_mbstowcs(wcs_srcname, logr_sysname, srcname_len / 2); + sh_len = srcname_len + hostname_len; + str_offs = 12 * sizeof (DWORD) + 4 * sizeof (WORD) + sh_len; + } + + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * logr_get_snapshot + * + * Allocate memory and make a copy, as a snapshot, from system log. + */ +static read_data_t * +logr_get_snapshot(void) +{ + read_data_t *data = NULL; + return (data); +} + +/* + * logr_s_EventLogQueryCount + * + * take a snapshot from system log, assign it to the given handle. + * return number of log entries in the snapshot as result of RPC + * call. + */ +/*ARGSUSED*/ +static int +logr_s_EventLogQueryCount(void *arg, struct mlrpc_xaction *mxa) +{ + struct logr_EventLogQueryCount *param = arg; + ms_handle_desc_t *desc; + read_data_t *data; + + if ((desc = mlsvc_lookup_handle((ms_handle_t *)¶m->handle)) == 0) { + param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE); + return (MLRPC_DRC_OK); + } + + if ((data = logr_get_snapshot()) == NULL) { + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + desc->discrim = (DWORD)data; + param->rec_num = data->tot_recnum; + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * logr_s_EventLogGetOldestRec + * + * Return oldest record number in the snapshot as result of RPC call. + */ +/*ARGSUSED*/ +static int +logr_s_EventLogGetOldestRec(void *arg, struct mlrpc_xaction *mxa) +{ + struct logr_EventLogGetOldestRec *param = arg; + ms_handle_desc_t *desc; + read_data_t *data; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->handle); + if (desc == NULL) { + param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE); + return (MLRPC_DRC_OK); + } + + data = (read_data_t *)desc->discrim; + param->oldest_rec = data->log.ix - data->tot_recnum; + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * set_event_typeid + * + * Map the local system log priority to the event type and event ID + * for Windows events. + */ +void +set_event_typeid(int le_pri, WORD *etype, DWORD *eid) +{ + switch (LOG_PRI(le_pri)) { + case LOG_EMERG: + case LOG_ALERT: + case LOG_CRIT: + case LOG_ERR: + *eid = EVENTID_SEVERITY_ERROR; + *etype = EVENTLOG_ERROR_TYPE; + break; + case LOG_WARNING: + *eid = EVENTID_SEVERITY_WARNING; + *etype = EVENTLOG_WARNING_TYPE; + break; + case LOG_NOTICE: + case LOG_INFO: + case LOG_DEBUG: + *eid = EVENTID_SEVERITY_INFO; + *etype = EVENTLOG_INFORMATION_TYPE; + break; + default: + *eid = EVENTID_SEVERITY_SUCCESS; + *etype = EVENTLOG_SUCCESS; + } +} + +static log_entry_t * +log_get_entry(struct log_info *linfo, int entno) +{ + return (&linfo->entry[entno]); +} + +/* + * Fill a Windows event record based on a local system log record. + */ +static void +set_logrec(log_entry_t *le, DWORD recno, logr_record_t *rec) +{ + int len; + + rec->Length1 = sizeof (logr_record_t); + rec->Reserved = 0x654C664C; + rec->RecordNumber = recno; + rec->TimeGenerated = le->timestamp.tv_sec; + rec->TimeWritten = le->timestamp.tv_sec; + set_event_typeid(le->pri, &rec->EventType, &rec->EventID); + rec->NumStrings = 1; + rec->EventCategory = 0; + rec->ReservedFlags = 0; + rec->ClosingRecordNumber = 0; + rec->StringOffset = str_offs; + rec->UserSidLength = 0; + rec->UserSidOffset = sizeof (logr_record_t) - sizeof (DWORD); + rec->DataLength = 0; + rec->DataOffset = sizeof (logr_record_t) - sizeof (DWORD); + bzero(rec->info, LOGR_INFOLEN); + (void) memcpy(rec->info, wcs_srcname, srcname_len); + (void) memcpy(rec->info + srcname_len, wcs_hostname, hostname_len); + + len = (LOGR_INFOLEN - sh_len) / 2; + + if ((strlen(le->msg) + 1) < len) + len = strlen(le->msg) + 1; + + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + (void) mts_mbstowcs((mts_wchar_t *)(rec->info+sh_len), le->msg, len); + rec->Length2 = sizeof (logr_record_t); +} + +/* + * logr_s_EventLogRead + * + * Reads a whole number of entries from system log. The function can + * read log entries in chronological or reverse chronological order. + */ +/*ARGSUSED*/ +static int +logr_s_EventLogRead(void *arg, struct mlrpc_xaction *mxa) +{ + struct logr_EventLogRead *param = arg; + ms_handle_desc_t *desc; + read_data_t *rdata; + log_entry_t *le; + DWORD ent_no, ent_num, ent_remain; + logr_record_t *rec; + BYTE *buf; + int dir, ent_per_req; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->handle); + if (desc == NULL) { + param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE); + return (MLRPC_DRC_OK); + } + + rdata = (read_data_t *)(desc->discrim); + if (rdata == 0) { + if ((rdata = logr_get_snapshot()) == NULL) { + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + desc->discrim = (DWORD)rdata; + } + + dir = (param->read_flags & EVENTLOG_FORWARDS_READ) ? FWD : REW; + + if (param->read_flags & EVENTLOG_SEEK_READ) { + rdata->last_sentrec = param->rec_offset; + } else if (rdata->first_read) { + /* + * set last record number which is read for + * the first iteration of sequential read. + */ + rdata->last_sentrec = (dir == FWD) + ? (rdata->log.ix - rdata->tot_recnum) : rdata->log.ix; + } + + ent_remain = (dir == FWD) + ? (rdata->tot_recnum - rdata->last_sentrec) : rdata->last_sentrec; + + /* + * function should return as many whole log entries as + * will fit in the buffer; it should not return partial + * entries, even if there is room in the buffer. + */ + ent_per_req = param->nbytes_to_read / sizeof (logr_record_t); + if (ent_remain > ent_per_req) + ent_remain = ent_per_req; + + if (ent_remain == 0) { + /* + * Send this error to Windows client so that it + * can figure out that there is no more record + * to read. + */ + param->sent_size = 0; + param->unknown = 0; + param->status = NT_SC_ERROR(NT_STATUS_END_OF_FILE); + return (MLRPC_DRC_OK); + } + + buf = (param->read_flags & EVENTLOG_SEEK_READ) + ? param->ru.rec : param->ru.recs; + + for (ent_num = 0, ent_no = rdata->last_sentrec; + ent_num < ent_remain; ent_num++, ent_no += dir) { + le = log_get_entry(&rdata->log, ent_no & NMSGMASK); + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + rec = (logr_record_t *)buf; + set_logrec(le, ent_no, rec); + buf += sizeof (logr_record_t); + } + rdata->last_sentrec = ent_no; + rdata->first_read = 0; + + param->sent_size = sizeof (logr_record_t) * ent_remain; + param->unknown = 0; + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * Declare extern references. + */ +DECL_FIXUP_STRUCT(logr_read_u); +DECL_FIXUP_STRUCT(logr_read_info); +DECL_FIXUP_STRUCT(logr_EventLogRead); + +/* + * Patch the logr_EventLogRead union. + * This function is called from mlsvc_logr_ndr.c + */ +void +fixup_logr_EventLogRead(void *xarg) +{ + struct logr_EventLogRead *arg = (struct logr_EventLogRead *)xarg; + unsigned short size1 = 0; + unsigned short size2 = 0; + unsigned short size3 = 0; + DWORD nbr = arg->nbytes_to_read; + + switch (nbr) { + case 0: + size1 = 0; + break; + default: + size1 = nbr; + break; + }; + + size2 = size1 + (2 * sizeof (DWORD)); + size3 = size2 + sizeof (mlrpcconn_request_hdr_t) + sizeof (DWORD); + + FIXUP_PDU_SIZE(logr_read_u, size1); + FIXUP_PDU_SIZE(logr_read_info, size2); + FIXUP_PDU_SIZE(logr_EventLogRead, size3); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_lsa.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_lsa.c new file mode 100644 index 0000000000..6aa4d716fe --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_lsa.c @@ -0,0 +1,1385 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + + +/* + * Local Security Authority RPC (LSARPC) server-side interface definition. + */ + +#include <unistd.h> +#include <strings.h> +#include <pwd.h> +#include <grp.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libmlsvc.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/lsarpc.ndl> +#include <smbsrv/lsalib.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/nterror.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/nmpipes.h> +#include <smbsrv/ntlocale.h> + +struct local_group_table { + WORD sid_name_use; + WORD domain_ix; + char *sid; + char *name; +}; + +static int lsarpc_s_CloseHandle(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_QuerySecurityObject(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_EnumAccounts(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_EnumTrustedDomain(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_OpenAccount(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_EnumPrivsAccount(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_LookupPrivValue(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_LookupPrivName(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_LookupPrivDisplayName(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_QueryInfoPolicy(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_OpenDomainHandle(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_OpenDomainHandle(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_LookupSids(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_LookupNames(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_GetConnectedUser(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_LookupSids2(void *arg, struct mlrpc_xaction *); +static int lsarpc_s_LookupNames2(void *arg, struct mlrpc_xaction *); + +static int lsarpc_s_PrimaryDomainInfo(struct mslsa_PrimaryDomainInfo *, + struct mlrpc_xaction *); +static int lsarpc_s_AccountDomainInfo(struct mslsa_AccountDomainInfo *, + struct mlrpc_xaction *); +static int lsarpc_s_LookupNtSid(struct mlrpc_xaction *, nt_sid_t *, + smb_userinfo_t *, struct mslsa_name_entry *, int); +static int lsarpc_s_LookupLocalSid(struct mlrpc_xaction *, nt_sid_t *, + smb_userinfo_t *, struct mslsa_name_entry *); +static int lsarpc_s_LookupBuiltinSid(struct mlrpc_xaction *, nt_sid_t *, + smb_userinfo_t *, struct mslsa_name_entry *); +static int lsarpc_s_UnknownSid(struct mlrpc_xaction *, nt_sid_t *, + smb_userinfo_t *, struct mslsa_name_entry *); +static int lsarpc_s_UpdateDomainTable(struct mlrpc_xaction *, + smb_userinfo_t *, struct mslsa_domain_table *, DWORD *); + +static int lsarpc_w2k_enable; + +static mlrpc_stub_table_t lsarpc_stub_table[] = { + { lsarpc_s_CloseHandle, LSARPC_OPNUM_CloseHandle }, + { lsarpc_s_QuerySecurityObject, LSARPC_OPNUM_QuerySecurityObject }, + { lsarpc_s_EnumAccounts, LSARPC_OPNUM_EnumerateAccounts }, + { lsarpc_s_EnumTrustedDomain, LSARPC_OPNUM_EnumTrustedDomain }, + { lsarpc_s_OpenAccount, LSARPC_OPNUM_OpenAccount }, + { lsarpc_s_EnumPrivsAccount, LSARPC_OPNUM_EnumPrivsAccount }, + { lsarpc_s_LookupPrivValue, LSARPC_OPNUM_LookupPrivValue }, + { lsarpc_s_LookupPrivName, LSARPC_OPNUM_LookupPrivName }, + { lsarpc_s_LookupPrivDisplayName, LSARPC_OPNUM_LookupPrivDisplayName }, + { lsarpc_s_QueryInfoPolicy, LSARPC_OPNUM_QueryInfoPolicy }, + { lsarpc_s_OpenDomainHandle, LSARPC_OPNUM_OpenPolicy }, + { lsarpc_s_OpenDomainHandle, LSARPC_OPNUM_OpenPolicy2 }, + { lsarpc_s_LookupSids, LSARPC_OPNUM_LookupSids }, + { lsarpc_s_LookupNames, LSARPC_OPNUM_LookupNames }, + { lsarpc_s_GetConnectedUser, LSARPC_OPNUM_GetConnectedUser }, + { lsarpc_s_LookupSids2, LSARPC_OPNUM_LookupSids2 }, + { lsarpc_s_LookupNames2, LSARPC_OPNUM_LookupNames2 }, + {0} +}; + +static mlrpc_service_t lsarpc_service = { + "LSARPC", /* name */ + "Local Security Authority", /* desc */ + "\\lsarpc", /* endpoint */ + PIPE_LSASS, /* sec_addr_port */ + "12345778-1234-abcd-ef000123456789ab", 0, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(lsarpc_interface), /* interface ti */ + lsarpc_stub_table /* stub_table */ +}; + +/* + * Windows 2000 interface. + */ +static mlrpc_service_t lsarpc_w2k_service = { + "LSARPC_W2K", /* name */ + "Local Security Authority", /* desc */ + "\\lsarpc", /* endpoint */ + PIPE_LSASS, /* sec_addr_port */ + "3919286a-b10c-11d0-9ba800c04fd92ef5", 0, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(lsarpc_interface), /* interface ti */ + lsarpc_stub_table /* stub_table */ +}; + +/* + * lsarpc_initialize + * + * This function registers the LSA RPC interface with the RPC runtime + * library. It must be called in order to use either the client side + * or the server side functions. + */ +void +lsarpc_initialize(void) +{ + (void) mlrpc_register_service(&lsarpc_service); + + if (lsarpc_w2k_enable) + (void) mlrpc_register_service(&lsarpc_w2k_service); +} + +/* + * lsarpc_s_OpenDomainHandle opnum=0x06 + * + * This is a request to open the LSA (OpenPolicy and OpenPolicy2). + * The client is looking for an LSA domain handle. Handles appear to + * be a 20 byte opaque object with the top 4 bytes all zero. As it is + * opaque to the client, we can put anything we like in it. Real handles + * do appear to contain a sequence number which is incremented when a + * new handle is issued. However, we don't really care about that + * (yet). Always return MLRPC_DRC_OK. + */ +/*ARGSUSED*/ +static int +lsarpc_s_OpenDomainHandle(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_OpenPolicy2 *param = arg; + + bzero(¶m->domain_handle, sizeof (mslsa_handle_t)); + (void) strcpy((char *)¶m->domain_handle.hand2, "DomainHandle"); + param->status = 0; + + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_CloseHandle opnum=0x00 + * + * This is a request to close the LSA interface specified by the handle. + * We don't track handles (yet), so just zero out the handle and return + * MLRPC_DRC_OK. Setting the handle to zero appears to be standard + * behaviour and someone may rely on it, i.e. we do on the client side. + */ +/*ARGSUSED*/ +static int +lsarpc_s_CloseHandle(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_CloseHandle *param = arg; + + bzero(¶m->result_handle, sizeof (param->result_handle)); + param->status = 0; + + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_QuerySecurityObject + */ +/*ARGSUSED*/ +static int +lsarpc_s_QuerySecurityObject(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_QuerySecurityObject *param = arg; + + bzero(param, sizeof (struct mslsa_QuerySecurityObject)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_EnumAccounts + * + * Enumerate the list of local accounts SIDs. The client should supply + * a valid OpenPolicy2 handle. The enum_context is used to support + * multiple enumeration calls to obtain the complete list of SIDs. + * It should be set to 0 on the first call and passed unchanged on + * subsequent calls until there are no more accounts - the server will + * return NT_SC_WARNING(MLSVC_NO_MORE_DATA). + * + * For now just set the status to access-denied. Note that we still have + * to provide a valid address for enum_buf because it's a reference and + * the marshalling rules require that references must not be null. + * The enum_context is used to support multiple + */ +static int +lsarpc_s_EnumAccounts(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_EnumerateAccounts *param = arg; + struct mslsa_EnumAccountBuf *enum_buf; + + bzero(param, sizeof (struct mslsa_EnumerateAccounts)); + + enum_buf = MLRPC_HEAP_NEW(mxa, struct mslsa_EnumAccountBuf); + if (enum_buf == NULL) { + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + bzero(enum_buf, sizeof (struct mslsa_EnumAccountBuf)); + param->enum_buf = enum_buf; + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + + +/* + * lsarpc_s_EnumTrustedDomain + * + * This is the server side function for handling requests to enumerate + * the list of trusted domains: currently held in the NT domain database. + * This call requires an OpenPolicy2 handle. The enum_context is used to + * support multiple enumeration calls to obtain the complete list. + * It should be set to 0 on the first call and passed unchanged on + * subsequent calls until there are no more accounts - the server will + * return NT_SC_WARNING(MLSVC_NO_MORE_DATA). + * + * For now just set the status to access-denied. Note that we still have + * to provide a valid address for enum_buf because it's a reference and + * the marshalling rules require that references must not be null. + */ +static int +lsarpc_s_EnumTrustedDomain(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_EnumTrustedDomain *param = arg; + struct mslsa_EnumTrustedDomainBuf *enum_buf; + + bzero(param, sizeof (struct mslsa_EnumTrustedDomain)); + + enum_buf = MLRPC_HEAP_NEW(mxa, struct mslsa_EnumTrustedDomainBuf); + if (enum_buf == NULL) { + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + bzero(enum_buf, sizeof (struct mslsa_EnumTrustedDomainBuf)); + param->enum_buf = enum_buf; + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + + +/* + * lsarpc_s_OpenAccount + * + * This is a request to open an account handle. This function hasn't + * been tested. It is just a template in case some server somewhere + * makes this call. See lsarpc_s_OpenDomainHandle for more information. + */ +/*ARGSUSED*/ +static int +lsarpc_s_OpenAccount(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_OpenAccount *param = arg; + + if (param->handle.hand1 != 0 || + strcmp("DomainHandle", (char *)¶m->handle.hand2)) { + param->status = NT_SC_ERROR(ERROR_NO_SUCH_DOMAIN); + } else { + (void) strcpy((char *)¶m->account_handle.hand2, + "AccountHandle"); + param->status = 0; + } + + return (MLRPC_DRC_OK); +} + + +/* + * lsarpc_s_EnumPrivsAccount + * + * This is the server side function for handling requests for account + * privileges. For now just set the status to not-supported status and + * return MLRPC_DRC_OK. Note that we still have to provide a valid + * address for enum_buf because it's a reference and the marshalling + * rules require that references must not be null. + */ +/*ARGSUSED*/ +static int +lsarpc_s_EnumPrivsAccount(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_EnumPrivsAccount *param = arg; + + bzero(param, sizeof (struct mslsa_EnumPrivsAccount)); + param->status = NT_SC_ERROR(NT_STATUS_NOT_SUPPORTED); + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_LookupPrivValue + * + * Server side function used to map a privilege name to a locally unique + * identifier (LUID). + */ +/*ARGSUSED*/ +static int +lsarpc_s_LookupPrivValue(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_LookupPrivValue *param = arg; + smb_privinfo_t *pi; + + if ((pi = smb_priv_getbyname((char *)param->name.str)) == NULL) { + bzero(param, sizeof (struct mslsa_LookupPrivValue)); + param->status = NT_SC_ERROR(NT_STATUS_NO_SUCH_PRIVILEGE); + return (MLRPC_DRC_OK); + } + + param->luid.low_part = pi->id; + param->luid.high_part = 0; + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_LookupPrivName + * + * Server side function used to map a locally unique identifier (LUID) + * to the appropriate privilege name string. + */ +static int +lsarpc_s_LookupPrivName(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_LookupPrivName *param = arg; + smb_privinfo_t *pi; + int rc; + + if ((pi = smb_priv_getbyvalue(param->luid.low_part)) == NULL) { + bzero(param, sizeof (struct mslsa_LookupPrivName)); + param->status = NT_SC_ERROR(NT_STATUS_NO_SUCH_PRIVILEGE); + return (MLRPC_DRC_OK); + } + + param->name = MLRPC_HEAP_NEW(mxa, mslsa_string_t); + if (param->name == NULL) { + bzero(param, sizeof (struct mslsa_LookupPrivName)); + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + rc = mlsvc_string_save((ms_string_t *)param->name, pi->name, mxa); + if (rc == 0) { + bzero(param, sizeof (struct mslsa_LookupPrivName)); + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_LookupPrivDisplayName + * + * This is the server side function for handling requests for account + * privileges. For now just set the status to not-supported status and + * return MLRPC_DRC_OK. + */ +static int +lsarpc_s_LookupPrivDisplayName(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_LookupPrivDisplayName *param = arg; + smb_privinfo_t *pi; + int rc; + + if ((pi = smb_priv_getbyname((char *)param->name.str)) == NULL) { + bzero(param, sizeof (struct mslsa_LookupPrivDisplayName)); + param->status = NT_SC_ERROR(NT_STATUS_NO_SUCH_PRIVILEGE); + return (MLRPC_DRC_OK); + } + + param->display_name = MLRPC_HEAP_NEW(mxa, mslsa_string_t); + if (param->display_name == NULL) { + bzero(param, sizeof (struct mslsa_LookupPrivDisplayName)); + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + rc = mlsvc_string_save((ms_string_t *)param->display_name, + pi->display_name, mxa); + + if (rc == 0) { + bzero(param, sizeof (struct mslsa_LookupPrivDisplayName)); + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + param->language_ret = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_GetConnectedUser + * + * This is still guesswork. Netmon doesn't know about this + * call and I'm not really sure what it is intended to achieve. + * Another packet capture application, Ethereal, calls this RPC as + * GetConnectedUser. + * We will receive our own hostname in the request and it appears + * we should respond with an account name and the domain name of connected + * user from the client that makes this call. + */ +static int +lsarpc_s_GetConnectedUser(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_GetConnectedUser *param = arg; + smb_dr_user_ctx_t *user_ctx = mxa->context->user_ctx; + DWORD status = NT_STATUS_SUCCESS; + int rc1; + int rc2; + + if (user_ctx == NULL) { + bzero(param, sizeof (struct mslsa_GetConnectedUser)); + status = NT_SC_ERROR(NT_STATUS_NO_TOKEN); + param->status = status; + return (MLRPC_DRC_OK); + } + + if (smb_getdomaininfo(0) == NULL) { + bzero(param, sizeof (struct mslsa_GetConnectedUser)); + status = NT_SC_ERROR(NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + param->status = status; + return (MLRPC_DRC_OK); + } + + param->owner = MLRPC_HEAP_NEW(mxa, struct mslsa_string_desc); + param->domain = MLRPC_HEAP_NEW(mxa, struct mslsa_DomainName); + if (param->owner == NULL || param->domain == NULL) { + status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + param->status = status; + return (MLRPC_DRC_OK); + } + + param->domain->name = MLRPC_HEAP_NEW(mxa, struct mslsa_string_desc); + if (param->domain->name == NULL) { + status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + param->status = status; + return (MLRPC_DRC_OK); + } + + rc1 = mlsvc_string_save((ms_string_t *)param->owner, + user_ctx->du_account, mxa); + + rc2 = mlsvc_string_save((ms_string_t *)param->domain->name, + user_ctx->du_domain, mxa); + + if (rc1 == 0 || rc2 == 0) + status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + + param->status = status; + return (MLRPC_DRC_OK); +} + + +/* + * lsarpc_s_QueryInfoPolicy + * + * This is the server side function for handling LSA information policy + * queries. Currently, we only support primary domain and account + * domain queries. This is just a front end to switch on the request + * and hand it off to the appropriate function to actually deal with + * obtaining and building the response. + */ +static int +lsarpc_s_QueryInfoPolicy(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_QueryInfoPolicy *param = arg; + struct mslsa_PolicyInfo *info; + int result; + + info = (struct mslsa_PolicyInfo *)MLRPC_HEAP_MALLOC( + mxa, sizeof (struct mslsa_PolicyInfo)); + + info->switch_value = param->info_class; + + switch (param->info_class) { + case MSLSA_POLICY_PRIMARY_DOMAIN_INFO: + result = lsarpc_s_PrimaryDomainInfo(&info->ru.pd_info, mxa); + break; + + case MSLSA_POLICY_ACCOUNT_DOMAIN_INFO: + result = lsarpc_s_AccountDomainInfo(&info->ru.ad_info, mxa); + break; + + default: + result = (MLRPC_DRC_FAULT_PARAM_0_UNIMPLEMENTED); + break; + } + + param->info = info; + param->status = NT_STATUS_SUCCESS; + return (result); +} + + +/* + * lsarpc_s_PrimaryDomainInfo + * + * This is the service side function for handling primary domain policy + * queries. This will return the primary domain name and sid. This is + * currently a pass through interface so all we do is act as a proxy + * between the client and the DC. If there is no session, fake up the + * response with default values - useful for share mode. + * + * If the server name matches the local hostname, we should return + * the local domain SID. + */ +static int +lsarpc_s_PrimaryDomainInfo(struct mslsa_PrimaryDomainInfo *pd_info, + struct mlrpc_xaction *mxa) +{ + int security_mode; + smb_ntdomain_t *di; + nt_domain_t *ntdp; + nt_sid_t *sid; + char domain_name[MLSVC_DOMAIN_NAME_MAX]; + char *name; + DWORD status; + int rc; + + status = NT_STATUS_SUCCESS; + + security_mode = smb_get_security_mode(); + + if (security_mode != SMB_SECMODE_DOMAIN) { + rc = smb_gethostname(domain_name, MLSVC_DOMAIN_NAME_MAX, 1); + if (rc != 0) { + status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + } + + name = domain_name; + sid = nt_sid_dup(nt_domain_local_sid()); + } else { + if ((di = smb_getdomaininfo(0)) == 0) { + status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + } + + ntdp = nt_domain_lookup_name(di->domain); + if (ntdp == 0) { + (void) lsa_query_primary_domain_info(); + ntdp = nt_domain_lookup_name(di->domain); + } + + if (ntdp == 0) { + sid = nt_sid_gen_null_sid(); + name = di->domain; + } else { + sid = nt_sid_dup(ntdp->sid); + name = ntdp->name; + } + } + + if (sid == 0) { + status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + } + + if (mlsvc_string_save((ms_string_t *)&pd_info->name, name, mxa) == 0) + status = NT_STATUS_INSUFFICIENT_RESOURCES; + + if ((pd_info->sid = (struct mslsa_sid *)mlsvc_sid_save(sid, mxa)) == 0) + status = NT_STATUS_INSUFFICIENT_RESOURCES; + + free(sid); + + if (status != NT_STATUS_SUCCESS) + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + + return (MLRPC_DRC_OK); +} + + +/* + * lsarpc_s_AccountDomainInfo + * + * This is the service side function for handling account domain policy + * queries. This is where we return our local domain information so that + * NT knows who to query for information on local names and SIDs. The + * domain name is the local hostname. + */ +static int +lsarpc_s_AccountDomainInfo(struct mslsa_AccountDomainInfo *ad_info, + struct mlrpc_xaction *mxa) +{ + char domain_name[MLSVC_DOMAIN_NAME_MAX]; + nt_sid_t *domain_sid; + int rc; + + if (smb_gethostname(domain_name, MLSVC_DOMAIN_NAME_MAX, 1) != 0) + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + + if ((domain_sid = nt_domain_local_sid()) == NULL) + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + + rc = mlsvc_string_save((ms_string_t *)&ad_info->name, + domain_name, mxa); + if (rc == 0) + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + + ad_info->sid = (struct mslsa_sid *)mlsvc_sid_save(domain_sid, mxa); + if (ad_info->sid == NULL) + return (MLRPC_DRC_FAULT_OUT_OF_MEMORY); + + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_LookupNames + * + * This is the service side function for handling name lookup requests. + * Currently, we only support lookups of a single name. This is also a + * pass through interface so all we do is act as a proxy between the + * client and the DC. + */ +static int +lsarpc_s_LookupNames(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_LookupNames *param = arg; + struct mslsa_rid_entry *rids; + smb_userinfo_t *user_info = 0; + struct mslsa_domain_table *domain_table; + struct mslsa_domain_entry *domain_entry; + char *name = ""; + DWORD status = NT_STATUS_SUCCESS; + int rc = 0; + + if (param->name_table->n_entry != 1) + return (MLRPC_DRC_FAULT_PARAM_0_UNIMPLEMENTED); + + rids = MLRPC_HEAP_NEW(mxa, struct mslsa_rid_entry); + domain_table = MLRPC_HEAP_NEW(mxa, struct mslsa_domain_table); + domain_entry = MLRPC_HEAP_NEW(mxa, struct mslsa_domain_entry); + user_info = mlsvc_alloc_user_info(); + + if (rids == NULL || domain_table == NULL || + domain_entry == NULL || user_info == NULL) { + status = NT_STATUS_NO_MEMORY; + goto name_lookup_failed; + } + + name = (char *)param->name_table->names->str; + + rc = lsa_lookup_local(name, user_info); + if (rc < 0) { + status = NT_STATUS_NO_SUCH_USER; + goto name_lookup_failed; + } + + if (rc > 0) { + if (lsa_lookup_name(0, 0, name, user_info) != 0) { + status = NT_STATUS_NO_SUCH_USER; + goto name_lookup_failed; + } + } + + /* + * Set up the rid table. + */ + rids[0].sid_name_use = user_info->sid_name_use; + rids[0].rid = user_info->rid; + rids[0].domain_index = 0; + param->translated_sids.n_entry = 1; + param->translated_sids.rids = rids; + + /* + * Set up the domain table. + */ + domain_table->entries = domain_entry; + domain_table->n_entry = 1; + domain_table->max_n_entry = MLSVC_DOMAIN_MAX; + + rc = mlsvc_string_save((ms_string_t *)&domain_entry->domain_name, + user_info->domain_name, mxa); + + domain_entry->domain_sid = + (struct mslsa_sid *)mlsvc_sid_save(user_info->domain_sid, mxa); + + if (rc == 0 || domain_entry->domain_sid == NULL) { + status = NT_STATUS_NO_MEMORY; + goto name_lookup_failed; + } + + param->domain_table = domain_table; + param->mapped_count = 1; + param->status = 0; + + mlsvc_free_user_info(user_info); + return (MLRPC_DRC_OK); + +name_lookup_failed: + mlsvc_free_user_info(user_info); + bzero(param, sizeof (struct mslsa_LookupNames)); + param->status = NT_SC_ERROR(status); + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_LookupSids + * + * This is the service side function for handling sid lookup requests. + * We have to set up both the name table and the domain table in the + * response. For each SID, we check for UNIX domain (local lookup) or + * NT domain (DC lookup) and call the appropriate lookup function. This + * should resolve the SID to a name. Then we need to update the domain + * table and make the name entry point at the appropriate domain table + * entry. + * + * On success return 0. Otherwise return an RPC specific error code. + */ +static int +lsarpc_s_LookupSids(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslsa_LookupSids *param = arg; + struct mslsa_domain_table *domain_table; + struct mslsa_domain_entry *domain_entry; + struct mslsa_name_entry *names; + smb_userinfo_t *user_info; + nt_sid_t *sid; + DWORD n_entry; + int result; + int i; + + user_info = mlsvc_alloc_user_info(); + + n_entry = param->lup_sid_table.n_entry; + names = MLRPC_HEAP_NEWN(mxa, struct mslsa_name_entry, n_entry); + domain_table = MLRPC_HEAP_NEW(mxa, struct mslsa_domain_table); + domain_entry = MLRPC_HEAP_NEWN(mxa, struct mslsa_domain_entry, + MLSVC_DOMAIN_MAX); + + if (names == NULL || domain_table == NULL || + domain_entry == NULL || user_info == NULL) { + bzero(param, sizeof (struct mslsa_LookupSids)); + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + domain_table->entries = domain_entry; + domain_table->n_entry = 0; + domain_table->max_n_entry = MLSVC_DOMAIN_MAX; + + for (i = 0; i < n_entry; ++i) { + bzero(&names[i], sizeof (struct mslsa_name_entry)); + sid = (nt_sid_t *)param->lup_sid_table.entries[i].psid; + + if (nt_sid_is_local(sid)) { + result = lsarpc_s_LookupLocalSid(mxa, sid, user_info, + &names[i]); + } else { + result = lsarpc_s_LookupBuiltinSid(mxa, sid, user_info, + &names[i]); + + if (result != 0) + result = lsarpc_s_LookupNtSid(mxa, sid, + user_info, &names[i], 1); + + if (result != 0) { + result = lsarpc_s_UnknownSid(mxa, sid, + user_info, &names[i]); + } + } + + if (result == -1) { + mlsvc_free_user_info(user_info); + param->domain_table = 0; + param->name_table.n_entry = 0; + param->name_table.entries = 0; + param->mapped_count = 0; + param->status = NT_SC_ERROR(NT_STATUS_INVALID_SID); + return (MLRPC_DRC_OK); + } + + result = lsarpc_s_UpdateDomainTable(mxa, user_info, + domain_table, &names[i].domain_ix); + + if (result == -1) { + mlsvc_free_user_info(user_info); + param->domain_table = 0; + param->name_table.n_entry = 0; + param->name_table.entries = 0; + param->mapped_count = 0; + param->status = NT_SC_ERROR(NT_STATUS_INVALID_SID); + return (MLRPC_DRC_OK); + } + + mlsvc_release_user_info(user_info); + } + + param->domain_table = domain_table; + param->name_table.n_entry = n_entry; + param->name_table.entries = names; + param->mapped_count = n_entry; + param->status = 0; + + mlsvc_free_user_info(user_info); + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_LookupLocalSid + * + * This function handles local domain SID lookup. If the SID matches the + * local domain SID, we lookup the local files to map the RID to a name. + * We attempt to handle both users and groups. When the SID was supplied + * to the client, the ID type should have been encoded in the RID. We + * decode the RID and lookup it up in either the passwd file or the + * group file as appropriate. + * + * On success, 0 is returned. Otherwise -1 is returned. + */ +static int +lsarpc_s_LookupLocalSid(struct mlrpc_xaction *mxa, nt_sid_t *sid, + smb_userinfo_t *user_info, struct mslsa_name_entry *name) +{ + char buffer[MLSVC_DOMAIN_NAME_MAX]; + char namebuf[MLSVC_DOMAIN_NAME_MAX]; + nt_sid_t *lds; + nt_sid_t *tmp_sid; + nt_group_t *grp; + struct passwd *pw; + struct group *gr; + DWORD rid; + int unix_id; + + if (smb_gethostname(buffer, MLSVC_DOMAIN_NAME_MAX, 1) != 0) + return (-1); + + /* + * Only free tmp_sid in error paths. If it is assigned to the + * user_info, it will be freed later when that structure is + * released. + */ + if ((tmp_sid = nt_sid_dup(sid)) == NULL) + return (-1); + + rid = 0; + lds = nt_domain_local_sid(); + user_info->sid_name_use = SidTypeInvalid; + + if (nt_sid_is_equal(lds, tmp_sid)) { + user_info->sid_name_use = SidTypeDomain; + user_info->name = strdup(buffer); + } else { + (void) nt_sid_split(tmp_sid, &rid); + + switch (SAM_RID_TYPE(rid)) { + case SAM_RT_NT_UID: + break; + + case SAM_RT_NT_GID: + user_info->sid_name_use = SidTypeAlias; + grp = nt_groups_lookup_rid(rid); + if (grp) + user_info->name = strdup(grp->name); + else { + (void) snprintf(namebuf, sizeof (namebuf), + "%d (no name)", rid); + user_info->name = strdup(namebuf); + } + break; + + case SAM_RT_UNIX_UID: + /* + * It is always possible that the rid will not + * correspond to an entry in the local passwd or group + * file. In this case we can return the RID with a + * message to indicate the problem, which seems better + * than returning an invalid SID error. + */ + unix_id = SAM_DECODE_RID(rid); + (void) snprintf(namebuf, sizeof (namebuf), + "%d (no name)", unix_id); + user_info->sid_name_use = SidTypeUser; + pw = getpwuid(unix_id); + user_info->name = (pw) ? + strdup(pw->pw_name) : strdup(namebuf); + break; + + case SAM_RT_UNIX_GID: + unix_id = SAM_DECODE_RID(rid); + (void) snprintf(namebuf, sizeof (namebuf), + "%d (no name)", unix_id); + user_info->sid_name_use = SidTypeAlias; + gr = getgrgid(unix_id); + user_info->name = (gr) ? + strdup(gr->gr_name) : strdup(namebuf); + break; + } + } + + if (user_info->sid_name_use == SidTypeInvalid) { + free(tmp_sid); + return (-1); + } + + /* + * Set up the rest of user_info. + * Don't free tmp_sid after this. + */ + user_info->rid = rid; + user_info->domain_name = strdup(buffer); + user_info->domain_sid = tmp_sid; + + bzero(name, sizeof (struct mslsa_name_entry)); + name->sid_name_use = user_info->sid_name_use; + + if (!mlsvc_string_save( + (ms_string_t *)&name->name, user_info->name, mxa)) { + return (-1); + } + + return (0); +} + +/* + * lsarpc_s_LookupNtSid + * + * This function handles NT domain SID lookup on the domain controller. + * Most of the work is performed by lsa_lookup_sid. We just have to + * update the name data for the response. It is assumed that any SID + * passed to this function has already been checked and correctly + * identified as an NT domain SID. It shouldn't break anything if you + * get it wrong, the domain controller will just reject the SID. + * + * On success, 0 is returned. Otherwise -1 is returned. + */ +static int +lsarpc_s_LookupNtSid(struct mlrpc_xaction *mxa, nt_sid_t *sid, + smb_userinfo_t *user_info, struct mslsa_name_entry *name, int version) +{ + char *username; + DWORD status; + + if (smb_getdomaininfo(0) == 0) { + status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + return (-1); + } + + if (version == 2) + status = lsa_lookup_sid2(sid, user_info); + else + status = lsa_lookup_sid(sid, user_info); + + if (status != 0) + return (-1); + + switch (user_info->sid_name_use) { + case SidTypeDomain: + if ((username = user_info->domain_name) == 0) + user_info->sid_name_use = SidTypeUnknown; + break; + + case SidTypeUser: + case SidTypeGroup: + case SidTypeAlias: + case SidTypeDeletedAccount: + case SidTypeWellKnownGroup: + if ((username = user_info->name) == 0) + user_info->sid_name_use = SidTypeUnknown; + break; + + default: + return (-1); + } + + if (username == 0) + username = "unknown"; + bzero(name, sizeof (struct mslsa_name_entry)); + name->sid_name_use = user_info->sid_name_use; + + if (!mlsvc_string_save((ms_string_t *)&name->name, username, mxa)) + return (-1); + + return (0); +} + +/* + * lsarpc_s_LookupBuiltinSid + * + * This function handles predefined local groups and aliases in the NT + * AUTHORITY or BUILTIN domains, and some other miscellaneous bits. I + * don't think NT cares about the domain field of well-known groups or + * aliases but it seems sensible to set it up anyway. If we get a match, + * set up the name in the response heap. + * + * On success, 0 is returned. Otherwise non-zero is returned. A non-zero + * return value should not be automatically interpreted as an error. The + * caller should attempt to resolve the SID through alternative means. + */ +static int +lsarpc_s_LookupBuiltinSid(struct mlrpc_xaction *mxa, nt_sid_t *sid, + smb_userinfo_t *user_info, struct mslsa_name_entry *name) +{ + char *np; + WORD sid_name_use; + + if ((np = nt_builtin_lookup_sid(sid, &sid_name_use)) == NULL) + return (1); + + user_info->sid_name_use = sid_name_use; + user_info->name = strdup(np); + user_info->domain_sid = nt_sid_dup(sid); + + if (user_info->name == NULL || user_info->domain_sid == NULL) { + mlsvc_release_user_info(user_info); + return (-1); + } + + if (sid_name_use != SidTypeDomain && sid->SubAuthCount != 0) + user_info->rid = sid->SubAuthority[sid->SubAuthCount - 1]; + else + user_info->rid = 0; + + if ((np = nt_builtin_lookup_domain(user_info->name)) != NULL) + user_info->domain_name = strdup(np); + else + user_info->domain_name = strdup("UNKNOWN"); + + if (user_info->domain_name == NULL) { + mlsvc_release_user_info(user_info); + return (-1); + } + + if (sid_name_use == SidTypeAlias && + user_info->domain_sid->SubAuthCount != 0) { + --user_info->domain_sid->SubAuthCount; + } + + bzero(name, sizeof (struct mslsa_name_entry)); + name->sid_name_use = sid_name_use; + + if (sid_name_use == SidTypeUnknown) { + mlsvc_release_user_info(user_info); + return (1); + } + + if (!mlsvc_string_save( + (ms_string_t *)&name->name, user_info->name, mxa)) { + mlsvc_release_user_info(user_info); + return (-1); + } + + return (0); +} + +/* + * lsarpc_s_UnknownSid + * + * This function handles unknown SIDs. By the time this is called we + * know that this is not a local SID and that the PDC has no idea to + * whom this sid refers. It may be a remnant from a time when the + * server was in another domain. All we can do is turn into the SID + * into a string and return it in place of a user name. + * + * On success, 0 is returned. Otherwise -1 is returned. + */ +static int +lsarpc_s_UnknownSid(struct mlrpc_xaction *mxa, nt_sid_t *sid, + smb_userinfo_t *user_info, struct mslsa_name_entry *name) +{ + char domain_name[MLSVC_DOMAIN_NAME_MAX]; + char *sidbuf; + + if ((sidbuf = nt_sid_format(sid)) == NULL) + return (-1); + + if (smb_gethostname(domain_name, MLSVC_DOMAIN_NAME_MAX, 1) != 0) + return (-1); + + (void) utf8_strupr(domain_name); + mlsvc_release_user_info(user_info); + user_info->sid_name_use = SidTypeUnknown; + user_info->name = sidbuf; + user_info->domain_name = strdup(domain_name); + user_info->domain_sid = nt_sid_dup(nt_domain_local_sid()); + + bzero(name, sizeof (struct mslsa_name_entry)); + name->sid_name_use = user_info->sid_name_use; + + if (!mlsvc_string_save( + (ms_string_t *)&name->name, user_info->name, mxa)) { + return (-1); + } + + return (0); +} + +/* + * lsarpc_s_UpdateDomainTable + * + * This routine is responsible for maintaining the domain table which + * will be returned from a SID lookup. Whenever a name is added to the + * name table, this function should be called with the corresponding + * domain name. If the domain information is not already in the table, + * it is added. On success return 0; Otherwise -1 is returned. + */ +static int +lsarpc_s_UpdateDomainTable(struct mlrpc_xaction *mxa, + smb_userinfo_t *user_info, struct mslsa_domain_table *domain_table, + DWORD *domain_idx) +{ + struct mslsa_domain_entry *dentry; + DWORD n_entry; + DWORD i; + + if (user_info->sid_name_use == SidTypeWellKnownGroup || + user_info->sid_name_use == SidTypeUnknown || + user_info->sid_name_use == SidTypeInvalid) { + /* + * These types don't need to reference an entry in the + * domain table. So return -1. + */ + *domain_idx = (DWORD)-1; + return (0); + } + + if ((dentry = domain_table->entries) == NULL) + return (-1); + + if ((n_entry = domain_table->n_entry) >= MLSVC_DOMAIN_MAX) + return (-1); + + for (i = 0; i < n_entry; ++i) { + if (nt_sid_is_equal((nt_sid_t *)dentry[i].domain_sid, + user_info->domain_sid)) { + *domain_idx = i; + return (0); + } + } + + if (i == MLSVC_DOMAIN_MAX) + return (-1); + + if (!mlsvc_string_save((ms_string_t *)&dentry[i].domain_name, + user_info->domain_name, mxa)) + return (-1); + + dentry[i].domain_sid = + (struct mslsa_sid *)mlsvc_sid_save(user_info->domain_sid, mxa); + + if (dentry[i].domain_sid == NULL) + return (-1); + + ++domain_table->n_entry; + *domain_idx = i; + return (0); +} + +/* + * lsarpc_s_LookupSids2 + * + * Other than the use of lsar_lookup_sids2 and lsar_name_entry2, this + * is identical to lsarpc_s_LookupSids. + */ +static int +lsarpc_s_LookupSids2(void *arg, struct mlrpc_xaction *mxa) +{ + struct lsar_lookup_sids2 *param = arg; + struct lsar_name_entry2 *names; + struct mslsa_domain_table *domain_table; + struct mslsa_domain_entry *domain_entry; + smb_userinfo_t *user_info; + nt_sid_t *sid; + DWORD n_entry; + int result; + int i; + + user_info = mlsvc_alloc_user_info(); + + n_entry = param->lup_sid_table.n_entry; + names = MLRPC_HEAP_NEWN(mxa, struct lsar_name_entry2, n_entry); + domain_table = MLRPC_HEAP_NEW(mxa, struct mslsa_domain_table); + domain_entry = MLRPC_HEAP_NEWN(mxa, struct mslsa_domain_entry, + MLSVC_DOMAIN_MAX); + + if (names == NULL || domain_table == NULL || + domain_entry == NULL || user_info == NULL) { + bzero(param, sizeof (struct lsar_lookup_sids2)); + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + domain_table->entries = domain_entry; + domain_table->n_entry = 0; + domain_table->max_n_entry = MLSVC_DOMAIN_MAX; + + for (i = 0; i < n_entry; ++i) { + bzero(&names[i], sizeof (struct lsar_name_entry2)); + sid = (nt_sid_t *)param->lup_sid_table.entries[i].psid; + + if (nt_sid_is_local(sid)) { + result = lsarpc_s_LookupLocalSid(mxa, sid, user_info, + (struct mslsa_name_entry *)&names[i]); + } else { + result = lsarpc_s_LookupBuiltinSid(mxa, sid, user_info, + (struct mslsa_name_entry *)&names[i]); + + if (result != 0) + result = lsarpc_s_LookupNtSid(mxa, sid, + user_info, + (struct mslsa_name_entry *)&names[i], 2); + + if (result != 0) { + result = lsarpc_s_UnknownSid(mxa, sid, + user_info, + (struct mslsa_name_entry *)&names[i]); + } + } + + if (result == -1) { + mlsvc_free_user_info(user_info); + param->domain_table = 0; + param->name_table.n_entry = 0; + param->name_table.entries = 0; + param->mapped_count = 0; + param->status = NT_SC_ERROR(NT_STATUS_INVALID_SID); + return (MLRPC_DRC_OK); + } + + result = lsarpc_s_UpdateDomainTable(mxa, user_info, + domain_table, &names[i].domain_ix); + + if (result == -1) { + mlsvc_free_user_info(user_info); + param->domain_table = 0; + param->name_table.n_entry = 0; + param->name_table.entries = 0; + param->mapped_count = 0; + param->status = NT_SC_ERROR(NT_STATUS_INVALID_SID); + + return (MLRPC_DRC_OK); + } + + mlsvc_release_user_info(user_info); + } + + param->domain_table = domain_table; + param->name_table.n_entry = n_entry; + param->name_table.entries = names; + param->mapped_count = n_entry; + param->status = 0; + + mlsvc_free_user_info(user_info); + return (MLRPC_DRC_OK); +} + +/* + * lsarpc_s_LookupNames2 + * + * Other than the use of lsar_LookupNames2 and lsar_rid_entry2, this + * is identical to lsarpc_s_LookupNames. + */ +static int +lsarpc_s_LookupNames2(void *arg, struct mlrpc_xaction *mxa) +{ + struct lsar_LookupNames2 *param = arg; + struct lsar_rid_entry2 *rids; + smb_userinfo_t *user_info = 0; + struct mslsa_domain_table *domain_table; + struct mslsa_domain_entry *domain_entry; + char *name = ""; + DWORD status = NT_STATUS_SUCCESS; + int rc = 0; + + if (param->name_table->n_entry != 1) + return (MLRPC_DRC_FAULT_PARAM_0_UNIMPLEMENTED); + + rids = MLRPC_HEAP_NEW(mxa, struct lsar_rid_entry2); + domain_table = MLRPC_HEAP_NEW(mxa, struct mslsa_domain_table); + domain_entry = MLRPC_HEAP_NEW(mxa, struct mslsa_domain_entry); + user_info = mlsvc_alloc_user_info(); + + if (rids == 0 || domain_table == 0 || + domain_entry == 0 || user_info == 0) { + status = NT_STATUS_NO_MEMORY; + goto name_lookup2_failed; + } + + name = (char *)param->name_table->names->str; + + rc = lsa_lookup_local(name, user_info); + if (rc < 0) { + status = NT_STATUS_NONE_MAPPED; + goto name_lookup2_failed; + } + + if (rc > 0) { + if (lsa_lookup_name2(0, 0, name, user_info) != 0) { + status = NT_STATUS_NONE_MAPPED; + goto name_lookup2_failed; + } + } + + /* + * Set up the rid table. + */ + bzero(rids, sizeof (struct lsar_rid_entry2)); + rids[0].sid_name_use = user_info->sid_name_use; + rids[0].rid = user_info->rid; + rids[0].domain_index = 0; + param->translated_sids.n_entry = 1; + param->translated_sids.rids = rids; + + /* + * Set up the domain table. + */ + domain_table->entries = domain_entry; + domain_table->n_entry = 1; + domain_table->max_n_entry = MLSVC_DOMAIN_MAX; + + rc = mlsvc_string_save((ms_string_t *)&domain_entry->domain_name, + user_info->domain_name, mxa); + + domain_entry->domain_sid = + (struct mslsa_sid *)mlsvc_sid_save(user_info->domain_sid, mxa); + + if (rc == 0 || domain_entry->domain_sid == NULL) { + status = NT_STATUS_NO_MEMORY; + goto name_lookup2_failed; + } + + param->domain_table = domain_table; + param->mapped_count = 1; + param->status = 0; + + mlsvc_free_user_info(user_info); + return (MLRPC_DRC_OK); + +name_lookup2_failed: + mlsvc_free_user_info(user_info); + bzero(param, sizeof (struct lsar_LookupNames2)); + param->status = NT_SC_ERROR(status); + return (MLRPC_DRC_OK); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_netr.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_netr.c new file mode 100644 index 0000000000..4edfcacd51 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_netr.c @@ -0,0 +1,202 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * NetLogon RPC (NETR) interface definition. This module provides + * the server side NETR RPC interface and the interface registration + * function. + */ + +#include <strings.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/netlogon.ndl> +#include <smbsrv/ntstatus.h> +#include <smbsrv/nterror.h> +#include <smbsrv/nmpipes.h> +#include <smbsrv/netrauth.h> + +static int netr_s_ServerReqChallenge(void *, struct mlrpc_xaction *); +static int netr_s_ServerAuthenticate2(void *, struct mlrpc_xaction *); +static int netr_s_ServerPasswordSet(void *, struct mlrpc_xaction *); +static int netr_s_SamLogon(void *, struct mlrpc_xaction *); +static int netr_s_SamLogoff(void *, struct mlrpc_xaction *); + +static mlrpc_stub_table_t netr_stub_table[] = { + { netr_s_ServerReqChallenge, NETR_OPNUM_ServerReqChallenge }, + { netr_s_ServerAuthenticate2, NETR_OPNUM_ServerAuthenticate2 }, + { netr_s_ServerPasswordSet, NETR_OPNUM_ServerPasswordSet }, + { netr_s_SamLogon, NETR_OPNUM_SamLogon }, + { netr_s_SamLogoff, NETR_OPNUM_SamLogoff }, + {0} +}; + +static mlrpc_service_t netr_service = { + "NETR", /* name */ + "NetLogon", /* desc */ + "\\netlogon", /* endpoint */ + PIPE_LSASS, /* sec_addr_port */ + "12345678-1234-abcd-ef0001234567cffb", 1, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(netr_interface), /* interface ti */ + netr_stub_table /* stub_table */ +}; + +/* + * netr_initialize + * + * This function registers the NETR RPC interface with the RPC runtime + * library. It must be called in order to use either the client side + * or the server side functions. + */ +void +netr_initialize(void) +{ + (void) mlrpc_register_service(&netr_service); +} + +/* + * netr_s_ServerReqChallenge + */ +/*ARGSUSED*/ +static int +netr_s_ServerReqChallenge(void *arg, struct mlrpc_xaction *mxa) +{ + struct netr_ServerReqChallenge *param = arg; + + bzero(param, sizeof (struct netr_ServerReqChallenge)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * netr_s_ServerAuthenticate2 + */ +/*ARGSUSED*/ +static int +netr_s_ServerAuthenticate2(void *arg, struct mlrpc_xaction *mxa) +{ + struct netr_ServerAuthenticate2 *param = arg; + + bzero(param, sizeof (struct netr_ServerAuthenticate2)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * netr_s_ServerPasswordSet + */ +/*ARGSUSED*/ +static int +netr_s_ServerPasswordSet(void *arg, struct mlrpc_xaction *mxa) +{ + struct netr_PasswordSet *param = arg; + + bzero(param, sizeof (struct netr_PasswordSet)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * netr_s_SamLogon + */ +/*ARGSUSED*/ +static int +netr_s_SamLogon(void *arg, struct mlrpc_xaction *mxa) +{ + struct netr_SamLogon *param = arg; + + bzero(param, sizeof (struct netr_SamLogon)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * netr_s_SamLogoff + */ +/*ARGSUSED*/ +static int +netr_s_SamLogoff(void *arg, struct mlrpc_xaction *mxa) +{ + struct netr_SamLogoff *param = arg; + + bzero(param, sizeof (struct netr_SamLogoff)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * Declare extern references. + */ +DECL_FIXUP_STRUCT(netr_validation_u); +DECL_FIXUP_STRUCT(netr_validation_info); +DECL_FIXUP_STRUCT(netr_SamLogon); + +/* + * Patch the netr_SamLogon union. + * This function is called from mlsvc_netr_ndr.c + */ +void +fixup_netr_SamLogon(struct netr_SamLogon *arg) +{ + unsigned short size1 = 0; + unsigned short size2 = 0; + unsigned short size3 = 0; + WORD level = (WORD)arg->validation_level; + + switch (level) { + case 3: + /* + * The netr_validation_u union contains a pointer, which + * is a DWORD in NDR. So we need to set size1 to ensure + * that we can correctly decode the remaining parameters. + */ + size1 = sizeof (DWORD); + break; + + default: + /* + * If the request is badly formed or the level is invalid, + * the server returns NT_STATUS_INVALID_INFO_CLASS. Size1 + * must be zero to correctly decode the status. + */ + size1 = 0; + break; + }; + + size2 = size1 + (2 * sizeof (DWORD)); + size3 = size2 + sizeof (mlrpcconn_request_hdr_t) + sizeof (DWORD); + + FIXUP_PDU_SIZE(netr_validation_u, size1); + FIXUP_PDU_SIZE(netr_validation_info, size2); + FIXUP_PDU_SIZE(netr_SamLogon, size3); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_sam.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_sam.c new file mode 100644 index 0000000000..3b51c05e71 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_sam.c @@ -0,0 +1,1463 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Security Accounts Manager RPC (SAMR) interface definition. + */ + +#include <strings.h> +#include <unistd.h> +#include <netdb.h> +#include <pwd.h> +#include <grp.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/ntsid.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/nmpipes.h> +#include <smbsrv/libmlsvc.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/samrpc.ndl> +#include <smbsrv/samlib.h> + +/* + * The keys associated with the various handles dispensed + * by the SAMR server. These keys can be used to validate + * client activity. These values are never passed over + * the network so security shouldn't be an issue. + */ +#define SAMR_CONNECT_KEY "SamrConnect" +#define SAMR_DOMAIN_KEY "SamrDomain" +#define SAMR_USER_KEY "SamrUser" +#define SAMR_GROUP_KEY "SamrGroup" +#define SAMR_ALIAS_KEY "SamrAlias" + +/* + * Domain discriminator values. Set the top bit to try + * to distinguish these values from user and group ids. + */ +#define SAMR_DATABASE_DOMAIN 0x80000001 +#define SAMR_LOCAL_DOMAIN 0x80000002 +#define SAMR_BUILTIN_DOMAIN 0x80000003 +#define SAMR_PRIMARY_DOMAIN 0x80000004 + +static DWORD samr_s_enum_local_domains(struct samr_EnumLocalDomain *, + struct mlrpc_xaction *); + +static mlrpc_stub_table_t samr_stub_table[]; + +static mlrpc_service_t samr_service = { + "SAMR", /* name */ + "Security Accounts Manager", /* desc */ + "\\samr", /* endpoint */ + PIPE_LSASS, /* sec_addr_port */ + "12345778-1234-abcd-ef000123456789ac", 1, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(samr_interface), /* interface ti */ + samr_stub_table /* stub_table */ +}; + +/* + * samr_initialize + * + * This function registers the SAM RPC interface with the RPC runtime + * library. It must be called in order to use either the client side + * or the server side functions. + */ +void +samr_initialize(void) +{ + (void) mlrpc_register_service(&samr_service); +} + +/* + * samr_s_ConnectAnon + * + * This is a request to connect to the local SAM database. We don't + * support any form of update request and our database doesn't + * contain any private information, so there is little point in + * doing any access access checking here. + * + * Return a handle for use with subsequent SAM requests. + */ +/*ARGSUSED*/ +static int +samr_s_ConnectAnon(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_ConnectAnon *param = arg; + ms_handle_t *handle; + + handle = mlsvc_get_handle(MLSVC_IFSPEC_SAMR, SAMR_CONNECT_KEY, + SAMR_DATABASE_DOMAIN); + bcopy(handle, ¶m->handle, sizeof (samr_handle_t)); + + param->status = 0; + return (MLRPC_DRC_OK); +} + +/* + * samr_s_CloseHandle + * + * This is a request to close the SAM interface specified by the handle. + * Free the handle and zero out the result handle for the client. + * + * We could do some checking here but it probably doesn't matter. + */ +/*ARGSUSED*/ +static int +samr_s_CloseHandle(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_CloseHandle *param = arg; + +#ifdef SAMR_S_DEBUG + if (mlsvc_lookup_handle((ms_handle_t *)¶m->handle) == 0) { + bzero(¶m->result_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE); + return (MLRPC_DRC_OK); + } +#endif /* SAMR_S_DEBUG */ + + (void) mlsvc_put_handle((ms_handle_t *)¶m->handle); + bzero(¶m->result_handle, sizeof (samr_handle_t)); + param->status = 0; + return (MLRPC_DRC_OK); +} + +/* + * samr_s_LookupDomain + * + * This is a request to map a domain name to a domain SID. We can map + * the primary domain name, our local domain name (hostname) and the + * builtin domain names to the appropriate SID. Anything else will be + * rejected. + */ +static int +samr_s_LookupDomain(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_LookupDomain *param = arg; + char resource_domain[MAXHOSTNAMELEN]; + char *domain_name; + char *p; + nt_sid_t *sid = NULL; + + if ((domain_name = (char *)param->domain_name.str) == NULL) { + bzero(param, sizeof (struct samr_LookupDomain)); + param->status = NT_SC_ERROR(NT_STATUS_INVALID_PARAMETER); + return (MLRPC_DRC_OK); + } + + smb_config_rdlock(); + p = smb_config_getstr(SMB_CI_DOMAIN_NAME); + (void) strlcpy(resource_domain, p, MAXHOSTNAMELEN); + smb_config_unlock(); + + if (mlsvc_is_local_domain(domain_name) == 1) { + sid = nt_sid_dup(nt_domain_local_sid()); + } else if (strcasecmp(resource_domain, domain_name) == 0) { + /* + * We should not be asked to provide + * the domain SID for the primary domain. + */ + sid = NULL; + } else { + sid = nt_builtin_lookup_name(domain_name, 0); + } + + if (sid) { + param->sid = (struct samr_sid *)mlsvc_sid_save(sid, mxa); + free(sid); + + if (param->sid == NULL) { + bzero(param, sizeof (struct samr_LookupDomain)); + param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + param->status = NT_STATUS_SUCCESS; + } else { + param->sid = NULL; + param->status = NT_SC_ERROR(NT_STATUS_NO_SUCH_DOMAIN); + } + + return (MLRPC_DRC_OK); +} + +/* + * samr_s_EnumLocalDomains + * + * This is a request for the local domains supported by this server. + * All we do here is validate the handle and set the status. The real + * work is done in samr_s_enum_local_domains. + */ +static int +samr_s_EnumLocalDomains(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_EnumLocalDomain *param = arg; + ms_handle_t *handle; + DWORD status; + + handle = (ms_handle_t *)¶m->handle; + + if (mlsvc_validate_handle(handle, SAMR_CONNECT_KEY) == 0) + status = NT_STATUS_ACCESS_DENIED; + else + status = samr_s_enum_local_domains(param, mxa); + + if (status == NT_STATUS_SUCCESS) { + param->enum_context = param->info->entries_read; + param->total_entries = param->info->entries_read; + param->status = NT_STATUS_SUCCESS; + } else { + bzero(param, sizeof (struct samr_EnumLocalDomain)); + param->status = NT_SC_ERROR(status); + } + + return (MLRPC_DRC_OK); +} + + +/* + * samr_s_enum_local_domains + * + * This function should only be called via samr_s_EnumLocalDomains to + * ensure that the appropriate validation is performed. We will answer + * queries about two domains: the local domain, synonymous with the + * local hostname, and the BUILTIN domain. So we return these two + * strings. + * + * Returns NT status values. + */ +static DWORD +samr_s_enum_local_domains(struct samr_EnumLocalDomain *param, + struct mlrpc_xaction *mxa) +{ + struct samr_LocalDomainInfo *info; + struct samr_LocalDomainEntry *entry; + char *hostname; + + hostname = MLRPC_HEAP_MALLOC(mxa, MAXHOSTNAMELEN); + if (hostname == NULL) + return (NT_STATUS_NO_MEMORY); + + if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0) + return (NT_STATUS_NO_MEMORY); + + entry = MLRPC_HEAP_NEWN(mxa, struct samr_LocalDomainEntry, 2); + if (entry == NULL) + return (NT_STATUS_NO_MEMORY); + + bzero(entry, (sizeof (struct samr_LocalDomainEntry) * 2)); + (void) mlsvc_string_save((ms_string_t *)&entry[0].name, hostname, mxa); + (void) mlsvc_string_save((ms_string_t *)&entry[1].name, "Builtin", mxa); + + info = MLRPC_HEAP_NEW(mxa, struct samr_LocalDomainInfo); + if (info == NULL) + return (NT_STATUS_NO_MEMORY); + + info->entries_read = 2; + info->entry = entry; + param->info = info; + return (NT_STATUS_SUCCESS); +} + +/* + * samr_s_OpenDomain + * + * This is a request to open a domain within the local SAM database. + * The caller must supply a valid handle obtained via a successful + * connect. We return a handle to be used to access objects within + * this domain. + */ +/*ARGSUSED*/ +static int +samr_s_OpenDomain(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_OpenDomain *param = arg; + ms_handle_t *handle = 0; + nt_domain_t *domain; + + if (!mlsvc_validate_handle( + (ms_handle_t *)¶m->handle, SAMR_CONNECT_KEY)) { + bzero(¶m->domain_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + } + + domain = nt_domain_lookup_sid((nt_sid_t *)param->sid); + if (domain == NULL) { + bzero(¶m->domain_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + return (MLRPC_DRC_OK); + } + + switch (domain->type) { + case NT_DOMAIN_BUILTIN: + handle = mlsvc_get_handle(MLSVC_IFSPEC_SAMR, + SAMR_DOMAIN_KEY, SAMR_BUILTIN_DOMAIN); + + bcopy(handle, ¶m->domain_handle, sizeof (samr_handle_t)); + param->status = 0; + break; + + case NT_DOMAIN_LOCAL: + handle = mlsvc_get_handle(MLSVC_IFSPEC_SAMR, + SAMR_DOMAIN_KEY, SAMR_LOCAL_DOMAIN); + + bcopy(handle, ¶m->domain_handle, sizeof (samr_handle_t)); + param->status = 0; + break; + + default: + bzero(¶m->domain_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + } + + return (MLRPC_DRC_OK); +} + +/* + * samr_s_QueryDomainInfo + * + * The caller should pass a domain handle. + * + * Windows 95 Server Manager sends requests for levels 6 and 7 when + * the services menu item is selected. Level 2 is basically for getting + * number of users, groups, and aliases in a domain. + * We have no information on what the various information levels mean. + */ +static int +samr_s_QueryDomainInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_QueryDomainInfo *param = arg; + ms_handle_desc_t *desc; + char *hostname; + char *domain_str = ""; + int rc; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->domain_handle); + if (desc == NULL || (strcmp(desc->key, SAMR_DOMAIN_KEY) != 0)) { + bzero(param, sizeof (struct samr_QueryDomainInfo)); + param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE); + return (MLRPC_DRC_OK); + } + + switch (param->info_level) { + case SAMR_QUERY_DOMAIN_INFO_6: + param->ru.info6.unknown1 = 0x00000000; + param->ru.info6.unknown2 = 0x00147FB0; + param->ru.info6.unknown3 = 0x00000000; + param->ru.info6.unknown4 = 0x00000000; + param->ru.info6.unknown5 = 0x00000000; + param->status = NT_STATUS_SUCCESS; + break; + + case SAMR_QUERY_DOMAIN_INFO_7: + param->ru.info7.unknown1 = 0x00000003; + param->status = NT_STATUS_SUCCESS; + break; + + case SAMR_QUERY_DOMAIN_INFO_2: + if (desc->discrim == SAMR_LOCAL_DOMAIN) { + hostname = MLRPC_HEAP_MALLOC(mxa, MAXHOSTNAMELEN); + rc = smb_gethostname(hostname, MAXHOSTNAMELEN, 1); + if (rc != 0 || hostname == NULL) { + bzero(param, + sizeof (struct samr_QueryDomainInfo)); + param->status = + NT_SC_ERROR(NT_STATUS_NO_MEMORY); + return (MLRPC_DRC_OK); + } + + domain_str = hostname; + } else { + if (desc->discrim == SAMR_BUILTIN_DOMAIN) + domain_str = "Builtin"; + } + + param->ru.info2.unknown1 = 0x00000000; + param->ru.info2.unknown2 = 0x80000000; + + (void) mlsvc_string_save((ms_string_t *)&(param->ru.info2.s1), + "", mxa); + + (void) mlsvc_string_save( + (ms_string_t *)&(param->ru.info2.domain), domain_str, mxa); + + (void) mlsvc_string_save((ms_string_t *)&(param->ru.info2.s2), + "", mxa); + + param->ru.info2.sequence_num = 0x0000002B; + param->ru.info2.unknown3 = 0x00000000; + param->ru.info2.unknown4 = 0x00000001; + param->ru.info2.unknown5 = 0x00000003; + param->ru.info2.unknown6 = 0x00000001; + param->ru.info2.num_users = 0; + param->ru.info2.num_groups = 0; + param->ru.info2.num_aliases = + (desc->discrim == SAMR_BUILTIN_DOMAIN) ? + nt_groups_count(NT_GROUP_CNT_BUILTIN) : + nt_groups_count(NT_GROUP_CNT_LOCAL); + + param->status = NT_STATUS_SUCCESS; + break; + + default: + bzero(param, sizeof (struct samr_QueryDomainInfo)); + return (MLRPC_DRC_FAULT_REQUEST_OPNUM_INVALID); + }; + + param->address = (DWORD)¶m->ru; + param->switch_value = param->info_level; + return (MLRPC_DRC_OK); +} + +/* + * samr_s_LookupNames + * + * The definition for this interface is obviously wrong but I can't + * seem to get it to work the way I think it should. It should + * support multiple name lookup but I can only get one working for now. + */ +static int +samr_s_LookupNames(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_LookupNames *param = arg; + ms_handle_desc_t *desc; + struct passwd *pw; + struct group *gr; + nt_sid_t *sid; + nt_group_t *grp; + WORD rid_type; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->handle); + if (desc == 0 || (strcmp(desc->key, SAMR_DOMAIN_KEY) != 0)) { + bzero(param, sizeof (struct samr_LookupNames)); + param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE); + return (MLRPC_DRC_OK); + } + + if (param->n_entry != 1) { + bzero(param, sizeof (struct samr_LookupNames)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + } + + if (param->name.str == NULL) { + bzero(param, sizeof (struct samr_LookupNames)); + /* + * Windows NT returns NT_STATUS_NONE_MAPPED when the + * name is NULL. + * Windows 2000 returns STATUS_INVALID_ACCOUNT_NAME. + */ + param->status = NT_SC_ERROR(NT_STATUS_NONE_MAPPED); + return (MLRPC_DRC_OK); + } + + param->rids.rid = MLRPC_HEAP_NEW(mxa, DWORD); + param->rid_types.rid_type = MLRPC_HEAP_NEW(mxa, DWORD); + + if (desc->discrim == SAMR_BUILTIN_DOMAIN) { + sid = nt_builtin_lookup_name((char *)param->name.str, + &rid_type); + + if (sid != 0) { + param->rids.n_entry = 1; + (void) nt_sid_get_rid(sid, ¶m->rids.rid[0]); + param->rid_types.n_entry = 1; + param->rid_types.rid_type[0] = rid_type; + param->status = NT_STATUS_SUCCESS; + free(sid); + return (MLRPC_DRC_OK); + } + } else if (desc->discrim == SAMR_LOCAL_DOMAIN) { + grp = nt_group_getinfo((char *)param->name.str, RWLOCK_READER); + + if (grp != NULL) { + param->rids.n_entry = 1; + (void) nt_sid_get_rid(grp->sid, ¶m->rids.rid[0]); + param->rid_types.n_entry = 1; + param->rid_types.rid_type[0] = *grp->sid_name_use; + param->status = NT_STATUS_SUCCESS; + nt_group_putinfo(grp); + return (MLRPC_DRC_OK); + } + + if ((pw = getpwnam((const char *)param->name.str)) != NULL) { + param->rids.n_entry = 1; + param->rids.rid[0] = SAM_ENCODE_UXUID(pw->pw_uid); + param->rid_types.n_entry = 1; + param->rid_types.rid_type[0] = SidTypeUser; + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); + } + + if ((gr = getgrnam((const char *)param->name.str)) != NULL) { + param->rids.n_entry = 1; + param->rids.rid[0] = SAM_ENCODE_UXGID(gr->gr_gid); + param->rid_types.n_entry = 1; + param->rid_types.rid_type[0] = SidTypeAlias; + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); + } + } + + param->rids.n_entry = 0; + param->rid_types.n_entry = 0; + param->status = NT_SC_ERROR(NT_STATUS_NONE_MAPPED); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_OpenUser + * + * This is a request to open a user within a specified domain in the + * local SAM database. The caller must supply a valid domain handle, + * obtained via a successful domain open request. The user is + * specified by the rid in the request. + */ +/*ARGSUSED*/ +static int +samr_s_OpenUser(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_OpenUser *param = arg; + ms_handle_t *handle; + + if (!mlsvc_validate_handle( + (ms_handle_t *)¶m->handle, SAMR_DOMAIN_KEY)) { + bzero(¶m->user_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + } + + handle = mlsvc_get_handle(MLSVC_IFSPEC_SAMR, SAMR_USER_KEY, + param->rid); + bcopy(handle, ¶m->user_handle, sizeof (samr_handle_t)); + + /* + * Need QueryUserInfo(level 21). + */ + bzero(¶m->user_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_DeleteUser + * + * This is a request to delete a user within a specified domain in the + * local SAM database. The caller should supply a valid user handle but + * we deny access regardless. + */ +/*ARGSUSED*/ +static int +samr_s_DeleteUser(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_DeleteUser *param = arg; + + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_QueryUserInfo + * + * Returns: + * NT_STATUS_SUCCESS + * NT_STATUS_ACCESS_DENIED + * NT_STATUS_INVALID_INFO_CLASS + */ +/*ARGSUSED*/ +static int +samr_s_QueryUserInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_QueryUserInfo *param = arg; + + if (!mlsvc_validate_handle( + (ms_handle_t *)¶m->user_handle, SAMR_USER_KEY)) { + bzero(param, sizeof (struct samr_QueryUserInfo)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + } + + bzero(param, sizeof (struct samr_QueryUserInfo)); + param->status = 0; + return (MLRPC_DRC_OK); +} + +/* + * samr_s_QueryUserGroups + * + * This is a request to obtain a list of groups of which a user is a + * member. The user is identified from the handle, which contains an + * encoded uid in the discriminator field. + * + * Get complete list of groups and check for builtin domain. + */ +static int +samr_s_QueryUserGroups(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_QueryUserGroups *param = arg; + struct samr_UserGroupInfo *info; + ms_handle_desc_t *desc; + struct passwd *pw; + DWORD uid; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->user_handle); + if (desc == 0 || strcmp(desc->key, SAMR_USER_KEY)) { + bzero(param, sizeof (struct samr_QueryUserGroups)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + } + + info = MLRPC_HEAP_NEW(mxa, struct samr_UserGroupInfo); + info->groups = MLRPC_HEAP_NEW(mxa, struct samr_UserGroups); + + uid = SAM_DECODE_RID(desc->discrim); + + if ((pw = getpwuid(uid)) != 0) { + info->n_entry = 1; + info->groups->rid = SAM_ENCODE_UXGID(pw->pw_gid); + info->groups->attr = SE_GROUP_MANDATORY + | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; + param->info = info; + param->status = 0; + } else { + bzero(param, sizeof (struct samr_QueryUserGroups)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + } + + return (MLRPC_DRC_OK); +} + +/* + * samr_s_OpenGroup + * + * This is a request to open a group within the specified domain in the + * local SAM database. The caller must supply a valid domain handle, + * obtained via a successful domain open request. The group is + * specified by the rid in the request. If this is a local RID it + * should already be encoded with type information. + * + * We return a handle to be used to access information about this group. + */ +/*ARGSUSED*/ +static int +samr_s_OpenGroup(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_OpenGroup *param = arg; + ms_handle_t *handle; + + if (!mlsvc_validate_handle( + (ms_handle_t *)¶m->handle, SAMR_DOMAIN_KEY)) { + bzero(¶m->group_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + } + + handle = mlsvc_get_handle(MLSVC_IFSPEC_SAMR, SAMR_GROUP_KEY, + param->rid); + bcopy(handle, ¶m->group_handle, sizeof (samr_handle_t)); + + param->status = 0; + return (MLRPC_DRC_OK); +} + +/* + * samr_s_Connect + * + * This is a request to connect to the local SAM database. We don't + * support any form of update request and our database doesn't + * contain any private information, so there is little point in + * doing any access access checking here. + * + * Return a handle for use with subsequent SAM requests. + */ +/*ARGSUSED*/ +static int +samr_s_Connect(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_Connect *param = arg; + ms_handle_t *handle; + + handle = mlsvc_get_handle(MLSVC_IFSPEC_SAMR, + SAMR_CONNECT_KEY, SAMR_DATABASE_DOMAIN); + bcopy(handle, ¶m->handle, sizeof (samr_handle_t)); + + param->status = 0; + return (MLRPC_DRC_OK); +} + +/* + * samr_s_GetUserPwInfo + * + * This is a request to get a user's password. + */ +/*ARGSUSED*/ +static int +samr_s_GetUserPwInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_GetUserPwInfo *param = arg; + ms_handle_t *handle; + DWORD status = 0; + + handle = (ms_handle_t *)¶m->user_handle; + + if (!mlsvc_validate_handle(handle, SAMR_USER_KEY)) + status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + + bzero(param, sizeof (struct samr_GetUserPwInfo)); + param->status = status; + return (MLRPC_DRC_OK); +} + +/* + * samr_s_CreateUser + * + * This is a request to create a user within a specified domain in the + * local SAM database. We always deny access. + */ +/*ARGSUSED*/ +static int +samr_s_CreateUser(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_CreateUser *param = arg; + + bzero(¶m->user_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_ChangeUserPasswd + */ +/*ARGSUSED*/ +static int +samr_s_ChangeUserPasswd(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_ChangeUserPasswd *param = arg; + + bzero(param, sizeof (struct samr_ChangeUserPasswd)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_GetDomainPwInfo + */ +/*ARGSUSED*/ +static int +samr_s_GetDomainPwInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_GetDomainPwInfo *param = arg; + + bzero(param, sizeof (struct samr_GetDomainPwInfo)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_SetUserInfo + */ +/*ARGSUSED*/ +static int +samr_s_SetUserInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_SetUserInfo *param = arg; + + bzero(param, sizeof (struct samr_SetUserInfo)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_QueryDispInfo + * + * This function is supposed to return local users' information. + * As we don't support local users, this function dosen't send + * back any information. + * + * I added a peice of code that returns information for Administrator + * and Guest builtin users. All information are hard-coded which I get + * from packet captures. Currently, this peice of code is opt-out. + */ +/*ARGSUSED*/ +static int +samr_s_QueryDispInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_QueryDispInfo *param = arg; + ms_handle_desc_t *desc; + DWORD status = 0; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->domain_handle); + if (desc == NULL || (strcmp(desc->key, SAMR_DOMAIN_KEY) != 0)) + status = NT_STATUS_INVALID_HANDLE; + +#ifdef SAMR_SUPPORT_USER + if ((desc->discrim != SAMR_LOCAL_DOMAIN) || (param->start_idx != 0)) { + param->total_size = 0; + param->returned_size = 0; + param->switch_value = 1; + param->count = 0; + param->users = 0; + } else { + param->total_size = 328; + param->returned_size = 328; + param->switch_value = 1; + param->count = 2; + param->users = (struct user_disp_info *)MLRPC_HEAP_MALLOC(mxa, + sizeof (struct user_disp_info)); + + param->users->count = 2; + param->users->acct[0].index = 1; + param->users->acct[0].rid = 500; + param->users->acct[0].ctrl = 0x210; + + (void) mlsvc_string_save( + (ms_string_t *)¶m->users->acct[0].name, + "Administrator", mxa); + + (void) mlsvc_string_save( + (ms_string_t *)¶m->users->acct[0].fullname, + "Built-in account for administering the computer/domain", + mxa); + + bzero(¶m->users->acct[0].desc, sizeof (samr_string_t)); + + param->users->acct[1].index = 2; + param->users->acct[1].rid = 501; + param->users->acct[1].ctrl = 0x211; + + (void) mlsvc_string_save( + (ms_string_t *)¶m->users->acct[1].name, + "Guest", mxa); + + (void) mlsvc_string_save( + (ms_string_t *)¶m->users->acct[1].fullname, + "Built-in account for guest access to the computer/domain", + mxa); + + bzero(¶m->users->acct[1].desc, sizeof (samr_string_t)); + } +#else + param->total_size = 0; + param->returned_size = 0; + param->switch_value = 1; + param->count = 0; + param->users = 0; +#endif + param->status = status; + return (MLRPC_DRC_OK); +} + +/* + * samr_s_EnumDomainGroups + * + * + * This function is supposed to return local users' information. + * As we don't support local users, this function dosen't send + * back any information. + * + * I added a peice of code that returns information for a + * domain group as None. All information are hard-coded which I get + * from packet captures. Currently, this peice of code is opt-out. + */ +/*ARGSUSED*/ +static int +samr_s_EnumDomainGroups(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_EnumDomainGroups *param = arg; + ms_handle_desc_t *desc; + DWORD status = NT_STATUS_SUCCESS; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->domain_handle); + if (desc == NULL || (strcmp(desc->key, SAMR_DOMAIN_KEY) != 0)) + status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE); + + param->total_size = 0; + param->returned_size = 0; + param->switch_value = 3; + param->count = 0; + param->groups = 0; + param->status = status; + return (MLRPC_DRC_OK); + +#ifdef SAMR_SUPPORT_GROUPS + if ((desc->discrim != SAMR_LOCAL_DOMAIN) || (param->start_idx != 0)) { + param->total_size = 0; + param->returned_size = 0; + param->switch_value = 3; + param->count = 0; + param->groups = 0; + } else { + param->total_size = 64; + param->returned_size = 64; + param->switch_value = 3; + param->count = 1; + param->groups = (struct group_disp_info *)MLRPC_HEAP_MALLOC( + mxa, sizeof (struct group_disp_info)); + + param->groups->count = 1; + param->groups->acct[0].index = 1; + param->groups->acct[0].rid = 513; + param->groups->acct[0].ctrl = 0x7; + (void) mlsvc_string_save( + (ms_string_t *)¶m->groups->acct[0].name, "None", mxa); + + (void) mlsvc_string_save( + (ms_string_t *)¶m->groups->acct[0].desc, + "Ordinary users", mxa); + } + + param->status = NT_STATUS_SUCCESS; + return (MLRPC_DRC_OK); +#endif +} + +/* + * samr_s_OpenAlias + * + * Lookup for requested alias, if it exists return a handle + * for that alias. The alias domain sid should match with + * the passed domain handle. + */ +/*ARGSUSED*/ +static int +samr_s_OpenAlias(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_OpenAlias *param = arg; + ms_handle_desc_t *desc = 0; + ms_handle_t *handle; + nt_group_t *grp; + DWORD status = NT_STATUS_SUCCESS; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->domain_handle); + if (desc == 0 || (strcmp(desc->key, SAMR_DOMAIN_KEY) != 0)) { + status = NT_STATUS_INVALID_HANDLE; + goto open_alias_err; + } + + if (param->access_mask != SAMR_ALIAS_ACCESS_GET_INFO) { + status = NT_STATUS_ACCESS_DENIED; + goto open_alias_err; + } + + grp = nt_groups_lookup_rid(param->rid); + if (grp == 0) { + status = NT_STATUS_NO_SUCH_ALIAS; + goto open_alias_err; + } + + if (((desc->discrim == SAMR_LOCAL_DOMAIN) && + !nt_sid_is_local(grp->sid)) || + ((desc->discrim == SAMR_BUILTIN_DOMAIN) && + !nt_sid_is_builtin(grp->sid))) { + status = NT_STATUS_NO_SUCH_ALIAS; + goto open_alias_err; + } + + handle = mlsvc_get_handle(MLSVC_IFSPEC_SAMR, SAMR_ALIAS_KEY, + param->rid); + bcopy(handle, ¶m->alias_handle, sizeof (samr_handle_t)); + param->status = 0; + return (MLRPC_DRC_OK); + +open_alias_err: + bzero(¶m->alias_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(status); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_CreateDomainAlias + * + * Creates a local group in the security database, which is the + * security accounts manager (SAM) + * For more information you can look at MSDN page for NetLocalGroupAdd. + * This RPC is used by CMC and right now it returns access denied. + * The peice of code that creates a local group doesn't get compiled. + */ +/*ARGSUSED*/ +static int +samr_s_CreateDomainAlias(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_CreateDomainAlias *param = arg; + +#ifdef SAMR_SUPPORT_ADD_ALIAS + DWORD status = NT_STATUS_SUCCESS; + ms_handle_desc_t *desc = 0; + ms_handle_t *handle; + nt_group_t *grp; + char *alias_name; +#endif + bzero(¶m->alias_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + +#ifdef SAMR_SUPPORT_ADD_ALIAS + alias_name = param->alias_name.str; + if (alias_name == 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto create_alias_err; + } + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->domain_handle); + if (desc == 0 || + (desc->discrim != SAMR_LOCAL_DOMAIN) || + (strcmp(desc->key, SAMR_DOMAIN_KEY) != 0)) { + status = NT_STATUS_INVALID_HANDLE; + goto create_alias_err; + } + + /* + * Check access mask. User should be member of + * Administrators or Account Operators local group. + */ + status = nt_group_add(alias_name, 0, + NT_GROUP_AF_ADD | NT_GROUP_AF_LOCAL); + + if (status != NT_STATUS_SUCCESS) + goto create_alias_err; + + grp = nt_group_getinfo(alias_name, RWLOCK_READER); + if (grp == NULL) { + status = NT_STATUS_INTERNAL_ERROR; + goto create_alias_err; + } + + (void) nt_sid_get_rid(grp->sid, ¶m->rid); + nt_group_putinfo(grp); + handle = mlsvc_get_handle(MLSVC_IFSPEC_SAMR, SAMR_ALIAS_KEY, + param->rid); + bcopy(handle, ¶m->alias_handle, sizeof (samr_handle_t)); + + param->status = 0; + return (MLRPC_DRC_OK); + +create_alias_err: + bzero(¶m->alias_handle, sizeof (samr_handle_t)); + param->status = NT_SC_ERROR(status); + return (MLRPC_DRC_OK); +#endif +} + +/* + * samr_s_SetAliasInfo + * + * For more information you can look at MSDN page for NetLocalGroupSetInfo. + */ +/*ARGSUSED*/ +static int +samr_s_SetAliasInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_SetAliasInfo *param = arg; + DWORD status = NT_STATUS_SUCCESS; + + if (!mlsvc_validate_handle( + (ms_handle_t *)¶m->alias_handle, SAMR_ALIAS_KEY)) { + status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE); + } + + param->status = status; + return (MLRPC_DRC_OK); +} + +/* + * samr_s_QueryAliasInfo + * + * Retrieves information about the specified local group account + * by given handle. + */ +static int +samr_s_QueryAliasInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_QueryAliasInfo *param = arg; + ms_handle_desc_t *desc; + nt_group_t *grp; + DWORD status; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->alias_handle); + if (desc == NULL || (strcmp(desc->key, SAMR_ALIAS_KEY) != 0)) { + status = NT_STATUS_INVALID_HANDLE; + goto query_alias_err; + } + + grp = nt_groups_lookup_rid(desc->discrim); + if (grp == NULL) { + status = NT_STATUS_NO_SUCH_ALIAS; + goto query_alias_err; + } + + switch (param->level) { + case SAMR_QUERY_ALIAS_INFO_1: + param->ru.info1.level = param->level; + (void) mlsvc_string_save( + (ms_string_t *)¶m->ru.info1.name, grp->name, mxa); + + (void) mlsvc_string_save( + (ms_string_t *)¶m->ru.info1.desc, grp->comment, mxa); + + param->ru.info1.unknown = 1; + break; + + case SAMR_QUERY_ALIAS_INFO_3: + param->ru.info3.level = param->level; + (void) mlsvc_string_save( + (ms_string_t *)¶m->ru.info3.desc, grp->comment, mxa); + + break; + + default: + status = NT_STATUS_INVALID_INFO_CLASS; + goto query_alias_err; + }; + + param->address = (DWORD)¶m->ru; + param->status = 0; + return (MLRPC_DRC_OK); + +query_alias_err: + param->status = NT_SC_ERROR(status); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_DeleteDomainAlias + * + * Deletes a local group account and all its members from the + * security database, which is the security accounts manager (SAM) database. + * Only members of the Administrators or Account Operators local group can + * execute this function. + * For more information you can look at MSDN page for NetLocalGroupSetInfo. + * + * This RPC is used by CMC and right now it returns access denied. + * The peice of code that removes a local group doesn't get compiled. + */ +/*ARGSUSED*/ +static int +samr_s_DeleteDomainAlias(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_DeleteDomainAlias *param = arg; + +#ifdef SAMR_SUPPORT_DEL_ALIAS + ms_handle_desc_t *desc = 0; + nt_group_t *grp; + char *alias_name; + DWORD status; +#endif + + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + return (MLRPC_DRC_OK); + +#ifdef SAMR_SUPPORT_DEL_ALIAS + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->alias_handle); + if (desc == 0 || (strcmp(desc->key, SAMR_ALIAS_KEY) != 0)) { + status = NT_STATUS_INVALID_HANDLE; + goto delete_alias_err; + } + + grp = nt_groups_lookup_rid(desc->discrim); + if (grp == 0) { + status = NT_STATUS_NO_SUCH_ALIAS; + goto delete_alias_err; + } + + alias_name = strdup(grp->name); + if (alias_name == 0) { + status = NT_STATUS_NO_MEMORY; + goto delete_alias_err; + } + + status = nt_group_delete(alias_name); + free(alias_name); + if (status != NT_STATUS_SUCCESS) + goto delete_alias_err; + + param->status = 0; + return (MLRPC_DRC_OK); + +delete_alias_err: + param->status = NT_SC_ERROR(status); + return (MLRPC_DRC_OK); +#endif +} + +/* + * samr_s_EnumDomainAliases + * + * This function sends back a list which contains all local groups' name. + */ +static int +samr_s_EnumDomainAliases(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_EnumDomainAliases *param = arg; + ms_handle_desc_t *desc; + nt_group_t *grp = NULL; + DWORD status; + nt_group_iterator_t *gi; + nt_sid_t *local_sid; + nt_sid_t *builtin_sid; + nt_sid_t *sid; + DWORD cnt, skip; + struct name_rid *info; + + desc = mlsvc_lookup_handle((ms_handle_t *)¶m->domain_handle); + if (desc == NULL || (strcmp(desc->key, SAMR_DOMAIN_KEY) != 0)) { + status = NT_STATUS_INVALID_HANDLE; + goto enum_alias_err; + } + + local_sid = nt_domain_local_sid(); + builtin_sid = nt_builtin_lookup_name("BUILTIN", 0); + + if (desc->discrim == SAMR_LOCAL_DOMAIN) { + sid = local_sid; + } else if (desc->discrim == SAMR_BUILTIN_DOMAIN) { + sid = builtin_sid; + } else { + status = NT_STATUS_INVALID_HANDLE; + goto enum_alias_err; + } + + cnt = skip = 0; + gi = nt_group_open_iterator(); + nt_group_ht_lock(RWLOCK_READER); + while ((grp = nt_group_iterate(gi)) != 0) { + if (skip++ < param->resume_handle) + continue; + if (nt_sid_is_indomain(sid, grp->sid)) + cnt++; + } + nt_group_ht_unlock(); + nt_group_close_iterator(gi); + + param->aliases = (struct aliases_info *)MLRPC_HEAP_MALLOC(mxa, + sizeof (struct aliases_info) + (cnt-1) * sizeof (struct name_rid)); + + param->aliases->count = cnt; + param->aliases->address = cnt; + info = param->aliases->info; + + skip = 0; + gi = nt_group_open_iterator(); + nt_group_ht_lock(RWLOCK_READER); + while ((grp = nt_group_iterate(gi)) != NULL) { + if (skip++ < param->resume_handle) + continue; + if (nt_sid_is_indomain(sid, grp->sid)) { + (void) nt_sid_get_rid(grp->sid, &info->rid); + (void) mlsvc_string_save((ms_string_t *)&info->name, + grp->name, mxa); + + info++; + } + } + nt_group_ht_unlock(); + nt_group_close_iterator(gi); + + param->out_resume = cnt; + param->entries = cnt; + param->status = 0; + return (MLRPC_DRC_OK); + +enum_alias_err: + param->status = NT_SC_ERROR(status); + return (MLRPC_DRC_OK); +} + +/* + * samr_s_Connect3 + * + * This is the connect3 form of the connect request. It contains an + * extra parameter over samr_Connect. See samr_s_Connect for other + * details. NT returns an RPC fault - so we can do the same for now. + * Doing it this way should avoid the unsupported opnum error message + * appearing in the log. + */ +/*ARGSUSED*/ +static int +samr_s_Connect3(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_Connect3 *param = arg; + + bzero(param, sizeof (struct samr_Connect3)); + return (MLRPC_DRC_FAULT_REQUEST_OPNUM_INVALID); +} + + +/* + * samr_s_Connect4 + * + * This is the connect4 form of the connect request used by Windows XP. + * Returns an RPC fault for now. + */ +/*ARGSUSED*/ +static int +samr_s_Connect4(void *arg, struct mlrpc_xaction *mxa) +{ + struct samr_Connect4 *param = arg; + + bzero(param, sizeof (struct samr_Connect4)); + return (MLRPC_DRC_FAULT_REQUEST_OPNUM_INVALID); +} + +static mlrpc_stub_table_t samr_stub_table[] = { + { samr_s_ConnectAnon, SAMR_OPNUM_ConnectAnon }, + { samr_s_CloseHandle, SAMR_OPNUM_CloseHandle }, + { samr_s_LookupDomain, SAMR_OPNUM_LookupDomain }, + { samr_s_EnumLocalDomains, SAMR_OPNUM_EnumLocalDomains }, + { samr_s_OpenDomain, SAMR_OPNUM_OpenDomain }, + { samr_s_QueryDomainInfo, SAMR_OPNUM_QueryDomainInfo }, + { samr_s_LookupNames, SAMR_OPNUM_LookupNames }, + { samr_s_OpenUser, SAMR_OPNUM_OpenUser }, + { samr_s_DeleteUser, SAMR_OPNUM_DeleteUser }, + { samr_s_QueryUserInfo, SAMR_OPNUM_QueryUserInfo }, + { samr_s_QueryUserGroups, SAMR_OPNUM_QueryUserGroups }, + { samr_s_OpenGroup, SAMR_OPNUM_OpenGroup }, + { samr_s_Connect, SAMR_OPNUM_Connect }, + { samr_s_GetUserPwInfo, SAMR_OPNUM_GetUserPwInfo }, + { samr_s_CreateUser, SAMR_OPNUM_CreateUser }, + { samr_s_ChangeUserPasswd, SAMR_OPNUM_ChangeUserPasswd }, + { samr_s_GetDomainPwInfo, SAMR_OPNUM_GetDomainPwInfo }, + { samr_s_SetUserInfo, SAMR_OPNUM_SetUserInfo }, + { samr_s_Connect3, SAMR_OPNUM_Connect3 }, + { samr_s_Connect4, SAMR_OPNUM_Connect4 }, + { samr_s_QueryDispInfo, SAMR_OPNUM_QueryDispInfo }, + { samr_s_OpenAlias, SAMR_OPNUM_OpenAlias }, + { samr_s_CreateDomainAlias, SAMR_OPNUM_CreateDomainAlias }, + { samr_s_SetAliasInfo, SAMR_OPNUM_SetAliasInfo }, + { samr_s_QueryAliasInfo, SAMR_OPNUM_QueryAliasInfo }, + { samr_s_DeleteDomainAlias, SAMR_OPNUM_DeleteDomainAlias }, + { samr_s_EnumDomainAliases, SAMR_OPNUM_EnumDomainAliases }, + { samr_s_EnumDomainGroups, SAMR_OPNUM_EnumDomainGroups }, + {0} +}; + +/* + * There is a bug in the way that midl and the marshalling code handles + * unions so we need to fix some of the data offsets at runtime. The + * following macros and the fixup functions handle the corrections. + */ +DECL_FIXUP_STRUCT(samr_QueryDomainInfo_ru); +DECL_FIXUP_STRUCT(samr_QueryDomainInfoRes); +DECL_FIXUP_STRUCT(samr_QueryDomainInfo); + +DECL_FIXUP_STRUCT(samr_QueryAliasInfo_ru); +DECL_FIXUP_STRUCT(samr_QueryAliasInfoRes); +DECL_FIXUP_STRUCT(samr_QueryAliasInfo); + +DECL_FIXUP_STRUCT(QueryUserInfo_result_u); +DECL_FIXUP_STRUCT(QueryUserInfo_result); +DECL_FIXUP_STRUCT(samr_QueryUserInfo); + +void +fixup_samr_QueryDomainInfo(struct samr_QueryDomainInfo *val) +{ + unsigned short size1 = 0; + unsigned short size2 = 0; + unsigned short size3 = 0; + + switch (val->switch_value) { + CASE_INFO_ENT(samr_QueryDomainInfo, 2); + CASE_INFO_ENT(samr_QueryDomainInfo, 6); + CASE_INFO_ENT(samr_QueryDomainInfo, 7); + + default: + return; + }; + + size2 = size1 + (2 * sizeof (DWORD)); + size3 = size2 + sizeof (mlrpcconn_request_hdr_t) + sizeof (DWORD); + + FIXUP_PDU_SIZE(samr_QueryDomainInfo_ru, size1); + FIXUP_PDU_SIZE(samr_QueryDomainInfoRes, size2); + FIXUP_PDU_SIZE(samr_QueryDomainInfo, size3); +} + +void +fixup_samr_QueryAliasInfo(struct samr_QueryAliasInfo *val) +{ + unsigned short size1 = 0; + unsigned short size2 = 0; + unsigned short size3 = 0; + + switch (val->level) { + CASE_INFO_ENT(samr_QueryAliasInfo, 1); + CASE_INFO_ENT(samr_QueryAliasInfo, 3); + + default: + return; + }; + + size2 = size1 + (2 * sizeof (DWORD)); + size3 = size2 + sizeof (mlrpcconn_request_hdr_t) + sizeof (DWORD); + + FIXUP_PDU_SIZE(samr_QueryAliasInfo_ru, size1); + FIXUP_PDU_SIZE(samr_QueryAliasInfoRes, size2); + FIXUP_PDU_SIZE(samr_QueryAliasInfo, size3); +} + +void +fixup_samr_QueryUserInfo(struct samr_QueryUserInfo *val) +{ + unsigned short size1 = 0; + unsigned short size2 = 0; + unsigned short size3 = 0; + + switch (val->switch_index) { + CASE_INFO_ENT(samr_QueryUserInfo, 1); + CASE_INFO_ENT(samr_QueryUserInfo, 6); + CASE_INFO_ENT(samr_QueryUserInfo, 7); + CASE_INFO_ENT(samr_QueryUserInfo, 8); + CASE_INFO_ENT(samr_QueryUserInfo, 9); + CASE_INFO_ENT(samr_QueryUserInfo, 16); + + default: + return; + }; + + size2 = size1 + (2 * sizeof (DWORD)); + size3 = size2 + sizeof (mlrpcconn_request_hdr_t) + sizeof (DWORD); + + FIXUP_PDU_SIZE(QueryUserInfo_result_u, size1); + FIXUP_PDU_SIZE(QueryUserInfo_result, size2); + FIXUP_PDU_SIZE(samr_QueryUserInfo, size3); +} + +/* + * As long as there is only one entry in the union, there is no need + * to patch anything. + */ +/*ARGSUSED*/ +void +fixup_samr_QueryGroupInfo(struct samr_QueryGroupInfo *val) +{ +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_spoolss.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_spoolss.c new file mode 100644 index 0000000000..bac138cf09 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_spoolss.c @@ -0,0 +1,139 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Printing and Spooling RPC interface definition. + * A stub to resolve RPC requests to this service. + */ + +#include <smbsrv/ndl/spoolss.ndl> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/nmpipes.h> + +static mlrpc_stub_table_t spoolss_stub_table[]; + +static mlrpc_service_t spoolss_service = { + "SPOOLSS", /* name */ + "Print Spool Service", /* desc */ + "\\spoolss", /* endpoint */ + PIPE_SPOOLSS, /* sec_addr_port */ + "12345678-1234-abcd-ef000123456789ab", 1, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(spoolss_interface), /* interface ti */ + spoolss_stub_table /* stub_table */ +}; + +/* + * spoolss_initialize + * + * This function registers the SPOOLSS RPC interface with the RPC + * runtime library. It must be called in order to use either the + * client side or the server side functions. + */ +void +spoolss_initialize(void) +{ + (void) mlrpc_register_service(&spoolss_service); +} + +/* + * spoolss_s_OpenPrinter + * + * We don't offer print spooling support. It should be okay to + * set the status to access denied and return MLRPC_DRC_OK. + */ +static int +spoolss_s_OpenPrinter(void *arg, struct mlrpc_xaction *mxa) +{ + struct spoolss_OpenPrinter *param = arg; + + bzero(param, sizeof (struct spoolss_OpenPrinter)); + param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED); + + return (MLRPC_DRC_OK); +} + + +/* + * spoolss_s_stub + */ +static int +spoolss_s_stub(void *arg, struct mlrpc_xaction *mxa) +{ + return (MLRPC_DRC_FAULT_PARAM_0_UNIMPLEMENTED); +} + +static mlrpc_stub_table_t spoolss_stub_table[] = { + { spoolss_s_OpenPrinter, SPOOLSS_OPNUM_OpenPrinter }, + { spoolss_s_stub, SPOOLSS_OPNUM_GetJob }, + { spoolss_s_stub, SPOOLSS_OPNUM_DeletePrinter }, + { spoolss_s_stub, SPOOLSS_OPNUM_GetPrinterDriver }, + { spoolss_s_stub, SPOOLSS_OPNUM_DeletePrinterDriver }, + { spoolss_s_stub, SPOOLSS_OPNUM_AddPrintProcessor }, + { spoolss_s_stub, SPOOLSS_OPNUM_GetPrintProcessorDirectory }, + { spoolss_s_stub, SPOOLSS_OPNUM_AbortPrinter }, + { spoolss_s_stub, SPOOLSS_OPNUM_ReadPrinter }, + { spoolss_s_stub, SPOOLSS_OPNUM_WaitForPrinterChange }, + { spoolss_s_stub, SPOOLSS_OPNUM_AddForm }, + { spoolss_s_stub, SPOOLSS_OPNUM_DeleteForm }, + { spoolss_s_stub, SPOOLSS_OPNUM_GetForm }, + { spoolss_s_stub, SPOOLSS_OPNUM_SetForm }, + { spoolss_s_stub, SPOOLSS_OPNUM_EnumMonitors }, + { spoolss_s_stub, SPOOLSS_OPNUM_AddPort }, + { spoolss_s_stub, SPOOLSS_OPNUM_ConfigurePort }, + { spoolss_s_stub, SPOOLSS_OPNUM_DeletePort }, + { spoolss_s_stub, SPOOLSS_OPNUM_CreatePrinterIc }, + { spoolss_s_stub, SPOOLSS_OPNUM_PlayDescriptionPrinterIc }, + { spoolss_s_stub, SPOOLSS_OPNUM_DeletePrinterIc }, + { spoolss_s_stub, SPOOLSS_OPNUM_AddPrinterConnection }, + { spoolss_s_stub, SPOOLSS_OPNUM_DeletePrinterConnection }, + { spoolss_s_stub, SPOOLSS_OPNUM_PrinterMessageBox }, + { spoolss_s_stub, SPOOLSS_OPNUM_AddMonitor }, + { spoolss_s_stub, SPOOLSS_OPNUM_DeleteMonitor }, + { spoolss_s_stub, SPOOLSS_OPNUM_DeletePrintProcessor }, + { spoolss_s_stub, SPOOLSS_OPNUM_AddPrintProvider }, + { spoolss_s_stub, SPOOLSS_OPNUM_DeletePrintProvider }, + { spoolss_s_stub, SPOOLSS_OPNUM_ResetPrinter }, + { spoolss_s_stub, SPOOLSS_OPNUM_FindFirstChangeNotify }, + { spoolss_s_stub, SPOOLSS_OPNUM_FindNextChangeNotify }, + { spoolss_s_stub, SPOOLSS_OPNUM_RouterFindFirstNotify }, + { spoolss_s_stub, SPOOLSS_OPNUM_ReplyOpenPrinter }, + { spoolss_s_stub, SPOOLSS_OPNUM_RouterReplyPrinter }, + { spoolss_s_stub, SPOOLSS_OPNUM_ReplyClosePrinter }, + { spoolss_s_stub, SPOOLSS_OPNUM_AddPortEx }, + { spoolss_s_stub, SPOOLSS_OPNUM_RemoteFindFirstChangeNotify }, + { spoolss_s_stub, SPOOLSS_OPNUM_SpoolerInitialize }, + { spoolss_s_stub, SPOOLSS_OPNUM_ResetPrinterEx }, + { spoolss_s_stub, SPOOLSS_OPNUM_RouterRefreshChangeNotify }, + { spoolss_s_OpenPrinter, SPOOLSS_OPNUM_OpenPrinter2 }, + {0} +}; diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_srvsvc.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_srvsvc.c new file mode 100644 index 0000000000..2a9a1f52d0 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_srvsvc.c @@ -0,0 +1,1684 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Server Service RPC (SRVSVC) server-side interface definition. + * The server service provides a remote administration interface. + * + * This service uses NERR/Win32 error codes rather than NT status + * values. + */ + +#include <sys/errno.h> +#include <unistd.h> +#include <netdb.h> +#include <strings.h> +#include <time.h> +#include <tzfile.h> +#include <time.h> +#include <thread.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <smbsrv/smb_fsd.h> +#include <smbsrv/libsmb.h> +#include <smbsrv/libmlsvc.h> +#include <smbsrv/lmerr.h> +#include <smbsrv/nterror.h> +#include <smbsrv/nmpipes.h> +#include <smbsrv/cifs.h> +#include <smbsrv/netrauth.h> +#include <smbsrv/mlsvc.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/srvsvc.ndl> +#include <smbsrv/smb_common_door.h> + +#define SV_TYPE_SENT_BY_ME (SV_TYPE_WORKSTATION | SV_TYPE_SERVER | SV_TYPE_NT) + +static DWORD mlsvc_NetSessionEnumLevel0(struct mslm_infonres *, DWORD, + struct mlrpc_xaction *); +static DWORD mlsvc_NetSessionEnumLevel1(struct mslm_infonres *, DWORD, + struct mlrpc_xaction *); +static DWORD mlsvc_NetShareEnumLevel0(struct mslm_infonres *, DWORD, + struct mlrpc_xaction *, char); +static DWORD mlsvc_NetShareEnumLevel1(struct mslm_infonres *, DWORD, + struct mlrpc_xaction *, char); +static DWORD mlsvc_NetShareEnumLevel2(struct mslm_infonres *, DWORD, + struct mlrpc_xaction *, char); +static DWORD mlsvc_NetShareEnumLevel502(struct mslm_infonres *, DWORD, + struct mlrpc_xaction *, char); +static DWORD mlsvc_NetShareEnumCommon(struct mlrpc_xaction *, DWORD, + int, lmshare_info_t *, void *); +static int srvsvc_is_poweruser(struct mlrpc_xaction *); + +static char empty_string[1]; + +static mlrpc_stub_table_t srvsvc_stub_table[]; + +static mlrpc_service_t srvsvc_service = { + "SRVSVC", /* name */ + "Server services", /* desc */ + "\\srvsvc", /* endpoint */ + PIPE_NTSVCS, /* sec_addr_port */ + "4b324fc8-1670-01d3-12785a47bf6ee188", 3, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(srvsvc_interface), /* interface ti */ + srvsvc_stub_table /* stub_table */ +}; + +/* + * srvsvc_fix_comment + * + * The parser sometimes has problems with empty strings so we + * need to ensure that the comment field has something in it. + */ +static inline char * +srvsvc_fix_comment(char *original, char *alternative) +{ + if (original == 0 || strlen(original) == 0) + return (alternative); + + return (original); +} + +/* + * srvsvc_share_mkpath + * + * Create the share path required by the share enum calls. This function + * creates the path in a MLRPC heap buffer ready for use by the caller. + * + * Some Windows over-the-wire backup applications do not work unless a + * drive letter is present in the share path. We don't care about the + * drive letter since the path is fully qualified with the volume name. + * We can try using drive B since by default that letter isn't assigned + * and even if it conflicts, we should still be okay with the fully + * qualified path. + * + * Windows clients seem to be mostly okay with the forward slash in + * share paths but they cannot handle one immediately after the drive + * letter, i.e. D:/. For consistency we convert all the slashes in + * the path. + * + * Returns a pointer to a heap buffer containing the share path, which + * could be a null pointer if the heap allocation fails. + */ +static char * +srvsvc_share_mkpath(struct mlrpc_xaction *mxa, char *path) +{ + char tmpbuf[MAXPATHLEN]; + char *p; + + if (strlen(path) == 0) + return (MLRPC_HEAP_STRSAVE(mxa, path)); + + /* strip the volume from the path (/vol1/home -> /home) */ + p = strchr(path[0] == '/' ? &path[1] : path, '/'); + + (void) snprintf(tmpbuf, MAXPATHLEN, "%c:%s", 'B' + /* vattr.drive_letter */, p == NULL ? "/": p); + (void) strsubst(tmpbuf, '/', '\\'); + + return (MLRPC_HEAP_STRSAVE(mxa, tmpbuf)); +} + +/* + * srvsvc_add_autohome + * + * Add the autohome share for the user to the shares' list + * if autohome is enabled the share is not a permanent share. + */ +static int +srvsvc_add_autohome(struct mlrpc_xaction *mxa, char *username, DWORD i, + int level, char *infop) +{ + lmshare_info_t si; + DWORD status; + + if ((lmshare_getinfo(username, &si) == NERR_Success) && + (si.mode & LMSHRM_TRANS)) { + status = mlsvc_NetShareEnumCommon(mxa, i, level, &si, + (void *)infop); + if (status == ERROR_SUCCESS) + i++; + } + + return (i); +} + +/* + * srvsvc_initialize + * + * This function registers the SRVSVC RPC interface with the RPC runtime + * library. It must be called in order to use either the client side + * or the server side functions. + */ +void +srvsvc_initialize(void) +{ + (void) mlrpc_register_service(&srvsvc_service); +} + +/* + * srvsvc_s_NetConnectEnum + * + * Under construction. This is just enough to get the interface working. + * Current level 0 and level 1 connection info are supported. + * + * Level 1 request is made by 'srvmgr' (Server Manager) + * utility of NT Server part of NT Domain to MLRPC server + * while double click of share info icon. These values + * are currectly virtual to MLRPC client and does't + * reflect the real state of server. + */ +static int +srvsvc_s_NetConnectEnum(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetConnectEnum *param = arg; + struct mslm_NetConnectInfoBuf0 *ci0; + struct mslm_NetConnectInfoBuf1 *ci1; + DWORD status; + + status = ERROR_SUCCESS; + switch (param->info.level) { + case 0: + ci0 = MLRPC_HEAP_NEW(mxa, struct mslm_NetConnectInfoBuf0); + if (ci0 == 0) { + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + ci0->coni0_id = 0x17; + + param->info.ru.info0 + = MLRPC_HEAP_NEW(mxa, struct mslm_NetConnectInfo0); + + if (param->info.ru.info0 == 0) { + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + param->info.ru.info0->ci0 = ci0; + param->info.ru.info0->entries_read = 1; + + param->total_entries = 1; + param->resume_handle = 0; + break; + + case 1: + ci1 = MLRPC_HEAP_NEW(mxa, struct mslm_NetConnectInfoBuf1); + if (ci1 == 0) { + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + ci1->coni1_id = 0x17; + ci1->coni1_type = STYPE_IPC; + ci1->coni1_num_opens = 1; + ci1->coni1_num_users = 1; + ci1->coni1_time = 16; + ci1->coni1_username = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, "Administrator"); + + ci1->coni1_netname = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, "IPC$"); + + param->info.ru.info1 = MLRPC_HEAP_NEW(mxa, + struct mslm_NetConnectInfo1); + + if (param->info.ru.info1 == 0) { + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + param->info.ru.info1->ci1 = ci1; + param->info.ru.info1->entries_read = 1; + + param->total_entries = 1; + param->resume_handle = 0; + break; + + default: + status = ERROR_ACCESS_DENIED; + break; + } + + if (status != ERROR_SUCCESS) + bzero(param, sizeof (struct mslm_NetConnectEnum)); + + param->status = status; + return (MLRPC_DRC_OK); +} + + +/* + * srvsvc_s_NetFileEnum + * + * Under construction. The values used here are fictional values and + * bear no relation to any real values, living or otherwise. I just + * made them up to get the interface working. + */ +static int +srvsvc_s_NetFileEnum(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetFileEnum *param = arg; + struct mslm_NetFileInfoBuf3 *fi3; + + if (param->info.switch_value != 3) { + bzero(param, sizeof (struct mslm_NetFileEnum)); + param->status = ERROR_INVALID_LEVEL; + return (MLRPC_DRC_OK); + } + + fi3 = MLRPC_HEAP_NEW(mxa, struct mslm_NetFileInfoBuf3); + if (fi3 == 0) { + bzero(param, sizeof (struct mslm_NetFileEnum)); + param->status = ERROR_NOT_ENOUGH_MEMORY; + return (MLRPC_DRC_OK); + } + + fi3->fi3_id = 0xF5; + fi3->fi3_permissions = 0x23; + fi3->fi3_num_locks = 0; + fi3->fi3_pathname = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, "\\PIPE\\srvsvc"); + + fi3->fi3_username = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, "Administrator"); + + param->info.ru.info3 = MLRPC_HEAP_NEW(mxa, struct mslm_NetFileInfo3); + if (param->info.ru.info3 == 0) { + bzero(param, sizeof (struct mslm_NetFileEnum)); + param->status = ERROR_NOT_ENOUGH_MEMORY; + return (MLRPC_DRC_OK); + } + + param->info.ru.info3->fi3 = fi3; + param->info.ru.info3->entries_read = 1; + param->total_entries = 1; + + if (param->resume_handle) + *param->resume_handle = 0x5F; + + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + + +/* + * srvsvc_s_NetFileClose + * + * Under construction. This is just enough to get the interface working. + */ +/*ARGSUSED*/ +static int +srvsvc_s_NetFileClose(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetFileClose *param = arg; + + bzero(param, sizeof (struct mslm_NetFileClose)); + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + + +/* + * srvsvc_s_NetShareGetInfo + * + * This call is made by Windows2000 to get share information. There are + * probably other information levels but these are the only ones I've + * seen so far. + * + * Returns Win32 error codes. + */ +static int +srvsvc_s_NetShareGetInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct mlsm_NetShareGetInfo *param = arg; + struct mslm_NetShareGetInfo0 *info0; + struct mslm_NetShareGetInfo1 *info1; + struct mslm_NetShareGetInfo2 *info2; + struct mslm_NetShareGetInfo502 *info502; + struct mslm_NetShareGetInfo1005 *info1005; + struct lmshare_info si; + char shr_comment[LMSHR_COMMENT_MAX]; + DWORD status; + + status = lmshare_getinfo((char *)param->netname, &si); + if (status != NERR_Success) { + if (strcasecmp((const char *)param->netname, "IPC$") == 0) { + /* + * Windows clients don't send the \\PIPE path for IPC$. + */ + (void) memset(&si, 0, sizeof (lmshare_info_t)); + (void) strcpy(si.share_name, "IPC$"); + (void) strcpy(si.comment, "Remote IPC"); + si.stype = (int)(STYPE_IPC | STYPE_SPECIAL); + } else { + bzero(param, sizeof (struct mlsm_NetShareGetInfo)); + param->status = status; + return (MLRPC_DRC_OK); + } + } + + if (si.comment && strlen(si.comment)) + (void) snprintf(shr_comment, sizeof (shr_comment), "%s %s", + si.directory, si.comment); + else + (void) strcpy(shr_comment, si.directory); + + status = ERROR_SUCCESS; + + switch (param->level) { + case 0: + info0 = MLRPC_HEAP_NEW(mxa, struct mslm_NetShareGetInfo0); + if (info0 == 0) { + + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + info0->shi0_netname + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, si.share_name); + + param->result.ru.info0 = info0; + break; + + case 1: + info1 = MLRPC_HEAP_NEW(mxa, struct mslm_NetShareGetInfo1); + if (info1 == 0) { + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + info1->shi1_netname = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, si.share_name); + info1->shi1_comment = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, shr_comment); + info1->shi1_type = si.stype; + param->result.ru.info1 = info1; + break; + + case 2: + info2 = MLRPC_HEAP_NEW(mxa, struct mslm_NetShareGetInfo2); + if (info2 == 0) { + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + info2->shi2_netname = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, si.share_name); + info2->shi2_comment = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, shr_comment); + info2->shi2_path = + (unsigned char *)srvsvc_share_mkpath(mxa, si.directory); + info2->shi2_passwd = 0; + info2->shi2_type = si.stype; + info2->shi2_permissions = 0; + info2->shi2_max_uses = SHI_USES_UNLIMITED; + info2->shi2_current_uses = 0; + param->result.ru.info2 = info2; + break; + + case 1005: + info1005 = MLRPC_HEAP_NEW(mxa, struct mslm_NetShareGetInfo1005); + if (info1005 == 0) { + + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + info1005->shi1005_flags = 0; + param->result.ru.info1005 = info1005; + break; + + case 502: + /* + * Level 502 provides level 2 information plus a + * security descriptor. We don't support security + * descriptors on shares yet. + */ + info502 = MLRPC_HEAP_NEW(mxa, struct mslm_NetShareGetInfo502); + if (info502 == 0) { + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + info502->shi502_netname = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, si.share_name); + info502->shi502_comment = + (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, shr_comment); + info502->shi502_path = + (unsigned char *)srvsvc_share_mkpath(mxa, si.directory); + info502->shi502_passwd = 0; + info502->shi502_type = si.stype; + info502->shi502_permissions = 0; + info502->shi502_max_uses = SHI_USES_UNLIMITED; + info502->shi502_current_uses = 0; + info502->shi502_reserved = 0; + info502->shi502_security_descriptor = 0; + param->result.ru.info502 = info502; + break; + + default: + status = ERROR_ACCESS_DENIED; + break; + } + + if (status != ERROR_SUCCESS) + bzero(param, sizeof (struct mlsm_NetShareGetInfo)); + else + param->result.switch_value = param->level; + + param->status = status; + return (MLRPC_DRC_OK); +} + + +/* + * srvsvc_s_NetShareSetInfo + * + * This call is made by SrvMgr to set share information. + * Always returns ERROR_ACCESS_DENIED for now. + * + * Returns Win32 error codes. + */ +static int +srvsvc_s_NetShareSetInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct mlsm_NetShareSetInfo *param = arg; + + (void) memset(param, 0, sizeof (struct mlsm_NetShareSetInfo)); + param->parm_err_ptr = (DWORD)MLRPC_HEAP_MALLOC(mxa, sizeof (DWORD)); + param->parm_err = 0; + + smb_config_rdlock(); + if (smb_config_getyorn(SMB_CI_SRVSVC_SHRSET_ENABLE) != 0) + param->status = ERROR_SUCCESS; + else + param->status = ERROR_ACCESS_DENIED; + smb_config_unlock(); + + return (MLRPC_DRC_OK); +} + +/* + * srvsvc_s_NetSessionEnum + * + * Level 1 request is made by the 'srvmgr' (Server Manager) utility on + * NT Server when the user info icon is selected. + * + * Return Values + * If the function succeeds, the return value is NERR_Success. + * If the function fails, the return value can be one of the following + * error codes: + * + * ERROR_ACCESS_DENIED The user does not have access to the requested + * information. + * ERROR_INVALID_LEVEL The value specified for the level parameter is + * invalid. + * ERROR_INVALID_PARAMETER The specified parameter is invalid. + * ERROR_MORE_DATA More entries are available. Specify a large + * enough buffer to receive all entries. + * ERROR_NOT_ENOUGH_MEMORY Insufficient memory is available. + * NERR_ClientNameNotFound A session does not exist with the computer + * name. + * NERR_InvalidComputer The computer name is invalid. + * NERR_UserNotFound The user name could not be found. + */ +static int +srvsvc_s_NetSessionEnum(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetSessionEnum *param = arg; + struct mslm_infonres *infonres; + DWORD status; + DWORD n_sessions; + + infonres = MLRPC_HEAP_NEW(mxa, struct mslm_infonres); + if (infonres == 0) { + bzero(param, sizeof (struct mslm_NetSessionEnum)); + param->status = ERROR_NOT_ENOUGH_MEMORY; + return (MLRPC_DRC_OK); + } + + infonres->entriesread = 0; + infonres->entries = 0; + param->result.level = param->level; + param->result.bufptr.p = infonres; + param->total_entries = 1; + param->status = ERROR_SUCCESS; + + n_sessions = (DWORD) smb_dwncall_user_num(); + + switch (param->level) { + case 0: + status = mlsvc_NetSessionEnumLevel0(infonres, n_sessions, mxa); + break; + + case 1: + status = mlsvc_NetSessionEnumLevel1(infonres, n_sessions, mxa); + break; + + default: + status = ERROR_INVALID_LEVEL; + break; + } + + if (status != 0) { + bzero(param, sizeof (struct mslm_NetSessionEnum)); + param->status = status; + return (MLRPC_DRC_OK); + } + + param->resume_handle = 0; + param->total_entries = infonres->entriesread; + param->status = status; + return (MLRPC_DRC_OK); +} + +/* + * mlsvc_NetSessionEnumLevel0 + * + * Build the level 0 session information. + */ +/*ARGSUSED*/ +static DWORD +mlsvc_NetSessionEnumLevel0(struct mslm_infonres *infonres, DWORD n_sessions, + struct mlrpc_xaction *mxa) +{ + struct mslm_SESSION_INFO_0 *info0; + smb_dr_ulist_t *ulist; + smb_dr_user_ctx_t *user; + char *workstation; + char ipaddr_buf[INET_ADDRSTRLEN]; + int i, offset, cnt, total; + + info0 = MLRPC_HEAP_NEWN(mxa, struct mslm_SESSION_INFO_0, n_sessions); + if (info0 == 0) + return (ERROR_NOT_ENOUGH_MEMORY); + + ulist = malloc(sizeof (smb_dr_ulist_t)); + if (!ulist) + return (ERROR_NOT_ENOUGH_MEMORY); + + for (total = 0, offset = 0; + (cnt = smb_dwncall_get_users(offset, ulist)) > 0; + offset += cnt) { + for (i = 0; i < cnt && total < n_sessions; i++, total++) { + user = &ulist->dul_users[i]; + /* + * Ignore local tokens (IP address is zero). + */ + if (user->du_ipaddr == 0) { + total--; + smb_dr_ulist_free(ulist); + ulist = malloc(sizeof (smb_dr_ulist_t)); + if (!ulist) + return (ERROR_NOT_ENOUGH_MEMORY); + continue; + } + + if ((workstation = user->du_workstation) == 0) { + (void) inet_ntop(AF_INET, + (char *)&user->du_ipaddr, ipaddr_buf, + sizeof (ipaddr_buf)); + workstation = ipaddr_buf; + } + + info0[total].sesi0_cname = MLRPC_HEAP_STRSAVE(mxa, + workstation); + if (info0[total].sesi0_cname == 0) { + smb_dr_ulist_free(ulist); + return (ERROR_NOT_ENOUGH_MEMORY); + } + + } + smb_dr_ulist_free(ulist); + ulist = malloc(sizeof (smb_dr_ulist_t)); + if (!ulist) + return (ERROR_NOT_ENOUGH_MEMORY); + + } + + infonres->entriesread = total; + infonres->entries = info0; + return (ERROR_SUCCESS); +} + + +/* + * mlsvc_NetSessionEnumLevel1 + * + * Build the level 1 session information. + */ +/*ARGSUSED*/ +static DWORD +mlsvc_NetSessionEnumLevel1(struct mslm_infonres *infonres, DWORD n_sessions, + struct mlrpc_xaction *mxa) +{ + struct mslm_SESSION_INFO_1 *info1; + smb_dr_ulist_t *ulist; + smb_dr_user_ctx_t *user; + char *workstation; + char *account; + char ipaddr_buf[INET_ADDRSTRLEN]; + int i, offset, cnt, total; + + info1 = MLRPC_HEAP_NEWN(mxa, struct mslm_SESSION_INFO_1, n_sessions); + if (info1 == 0) + return (ERROR_NOT_ENOUGH_MEMORY); + + ulist = malloc(sizeof (smb_dr_ulist_t)); + if (!ulist) + return (ERROR_NOT_ENOUGH_MEMORY); + + for (total = 0, offset = 0; + (cnt = smb_dwncall_get_users(offset, ulist)) > 0; + offset += cnt) { + for (i = 0; i < cnt && total < n_sessions; i++, total++) { + user = &ulist->dul_users[i]; + /* + * Ignore local user_ctxs (IP address is zero). + */ + if (user->du_ipaddr == 0) { + total--; + smb_dr_ulist_free(ulist); + ulist = malloc(sizeof (smb_dr_ulist_t)); + if (!ulist) + return (ERROR_NOT_ENOUGH_MEMORY); + continue; + } + + if ((workstation = user->du_workstation) == 0) { + (void) inet_ntop(AF_INET, + (char *)&user->du_ipaddr, + ipaddr_buf, sizeof (ipaddr_buf)); + workstation = ipaddr_buf; + } + + if ((account = user->du_account) == 0) + account = "Unknown"; + + info1[total].sesi1_cname = MLRPC_HEAP_STRSAVE(mxa, + workstation); + info1[total].sesi1_uname = MLRPC_HEAP_STRSAVE(mxa, + account); + + if (info1[total].sesi1_cname == 0 || + info1[total].sesi1_uname == 0) { + smb_dr_ulist_free(ulist); + return (ERROR_NOT_ENOUGH_MEMORY); + } + + info1[total].sesi1_nopens = 1; + info1[total].sesi1_time = time(0) - + user->du_logon_time; + info1[total].sesi1_itime = 0; + info1[total].sesi1_uflags = + (user->du_flags & SMB_ATF_GUEST) ? SESS_GUEST : 0; + } + smb_dr_ulist_free(ulist); + ulist = malloc(sizeof (smb_dr_ulist_t)); + if (!ulist) + return (ERROR_NOT_ENOUGH_MEMORY); + } + + infonres->entriesread = total; + infonres->entries = info1; + return (ERROR_SUCCESS); +} + +/* + * srvsvc_s_NetSessionDel + * + * Ends a network session between a server and a workstation. + * On NT only members of the Administrators or Account Operators + * local groups are permitted to use NetSessionDel. + * + * Return Values + * If the function succeeds, the return value is NERR_Success/ + * ERROR_SUCCESS. If the function fails, the return value can be + * one of the following error codes: + * + * ERROR_ACCESS_DENIED The user does not have access to the + * requested information. + * ERROR_INVALID_PARAMETER The specified parameter is invalid. + * ERROR_NOT_ENOUGH_MEMORY Insufficient memory is available. + * NERR_ClientNameNotFound A session does not exist with that + * computer name. + */ +static int +srvsvc_s_NetSessionDel(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetSessionDel *param = arg; + + if (srvsvc_is_poweruser(mxa) == 0) { + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); + } + + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * SRVSVC NetServerGetInfo + * + * IN LPTSTR servername, + * IN DWORD level, + * OUT union switch(level) { + * case 100: mslm_SERVER_INFO_100 *p100; + * case 101: mslm_SERVER_INFO_101 *p101; + * case 102: mslm_SERVER_INFO_102 *p102; + * default: char *nullptr; + * } bufptr, + * OUT DWORD status + */ +static int +srvsvc_s_NetServerGetInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetServerGetInfo *param = arg; + struct mslm_SERVER_INFO_100 *info100; + struct mslm_SERVER_INFO_101 *info101; + struct mslm_SERVER_INFO_102 *info102; + char *sys_comment; + char hostname[MAXHOSTNAMELEN]; + + if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0) { +netservergetinfo_no_memory: + bzero(param, sizeof (struct mslm_NetServerGetInfo)); + return (ERROR_NOT_ENOUGH_MEMORY); + } + + smb_config_rdlock(); + sys_comment = smb_config_getstr(SMB_CI_SYS_CMNT); + sys_comment = srvsvc_fix_comment(sys_comment, " "); + smb_config_unlock(); + + switch (param->level) { + case 100: + info100 = MLRPC_HEAP_NEW(mxa, struct mslm_SERVER_INFO_100); + if (info100 == 0) + goto netservergetinfo_no_memory; + + bzero(info100, sizeof (struct mslm_SERVER_INFO_100)); + info100->sv100_platform_id = SV_PLATFORM_ID_NT; + info100->sv100_name + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, hostname); + + if (info100->sv100_name == 0) + goto netservergetinfo_no_memory; + + param->result.bufptr.bufptr100 = info100; + break; + + case 101: + info101 = MLRPC_HEAP_NEW(mxa, struct mslm_SERVER_INFO_101); + if (info101 == 0) + goto netservergetinfo_no_memory; + + bzero(info101, sizeof (struct mslm_SERVER_INFO_101)); + info101->sv101_platform_id = SV_PLATFORM_ID_NT; + info101->sv101_version_major = 4; + info101->sv101_version_minor = 0; + info101->sv101_type = SV_TYPE_SENT_BY_ME; + info101->sv101_name + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, hostname); + + info101->sv101_comment + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, sys_comment); + + if (info101->sv101_name == 0 || info101->sv101_comment == 0) + goto netservergetinfo_no_memory; + + param->result.bufptr.bufptr101 = info101; + break; + + case 102: + info102 = MLRPC_HEAP_NEW(mxa, struct mslm_SERVER_INFO_102); + if (info102 == 0) + goto netservergetinfo_no_memory; + + bzero(info102, sizeof (struct mslm_SERVER_INFO_102)); + info102->sv102_platform_id = SV_PLATFORM_ID_NT; + info102->sv102_version_major = 4; + info102->sv102_version_minor = 0; + info102->sv102_type = SV_TYPE_SENT_BY_ME; + info102->sv102_name + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, hostname); + + info102->sv102_comment + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, sys_comment); + + /* + * The following level 102 fields are defaulted to zero + * by virtue of the call to bzero above. + * + * sv102_users + * sv102_disc + * sv102_hidden + * sv102_announce + * sv102_anndelta + * sv102_licenses + * sv102_userpath + */ + if (info102->sv102_name == 0 || info102->sv102_comment == 0) + goto netservergetinfo_no_memory; + + param->result.bufptr.bufptr102 = info102; + break; + + default: + bzero(¶m->result, + sizeof (struct mslm_NetServerGetInfo_result)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); + } + + param->result.level = param->level; + param->status = (ERROR_SUCCESS); + return (MLRPC_DRC_OK); +} + +/* + * NetRemoteTOD + * + * Returns information about the time of day on this server. + * + * typedef struct _TIME_OF_DAY_INFO { + * DWORD tod_elapsedt; // seconds since 00:00:00 January 1 1970 GMT + * DWORD tod_msecs; // arbitrary milliseconds (since reset) + * DWORD tod_hours; // current hour [0-23] + * DWORD tod_mins; // current minute [0-59] + * DWORD tod_secs; // current second [0-59] + * DWORD tod_hunds; // current hundredth (0.01) second [0-99] + * LONG tod_timezone; // time zone of the server + * DWORD tod_tinterval; // clock tick time interval + * DWORD tod_day; // day of the month [1-31] + * DWORD tod_month; // month of the year [1-12] + * DWORD tod_year; // current year + * DWORD tod_weekday; // day of the week since sunday [0-6] + * } TIME_OF_DAY_INFO; + * + * The time zone of the server is calculated in minutes from Greenwich + * Mean Time (GMT). For time zones west of Greenwich, the value is + * positive; for time zones east of Greenwich, the value is negative. + * A value of -1 indicates that the time zone is undefined. + * + * The clock tick value represents a resolution of one ten-thousandth + * (0.0001) second. + */ +static int +srvsvc_s_NetRemoteTOD(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetRemoteTOD *param = arg; + struct mslm_TIME_OF_DAY_INFO *tod; + struct timeval time_val; + struct tm tm; + + (void) gettimeofday(&time_val, 0); + (void) gmtime_r(&time_val.tv_sec, &tm); + + tod = MLRPC_HEAP_NEW(mxa, struct mslm_TIME_OF_DAY_INFO); + if (tod == NULL) { + bzero(param, sizeof (struct mslm_NetRemoteTOD)); + return (ERROR_NOT_ENOUGH_MEMORY); + } + + tod->tod_elapsedt = time_val.tv_sec; + tod->tod_msecs = time_val.tv_usec; + tod->tod_hours = tm.tm_hour; + tod->tod_mins = tm.tm_min; + tod->tod_secs = tm.tm_sec; + tod->tod_hunds = 0; + tod->tod_tinterval = 1000; + tod->tod_day = tm.tm_mday; + tod->tod_month = tm.tm_mon+1; + tod->tod_year = tm.tm_year+1900; + tod->tod_weekday = tm.tm_wday; + + (void) localtime_r(&time_val.tv_sec, &tm); + + param->bufptr = tod; + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * srvsvc_s_NetNameValidate + * + * Perform name validation. + * I've observed that the Computer Management Windows Application + * always send this request with type=0x09 and the flags=0 when + * attempting to validate a share name. + * + * The share name is consider invalid if it contains any of the + * following character (as mentioned in MSDN article #236388). + * + * " / \ [ ] : | < > + ; , ? * = + * + * + * For now, if the type is other than 0x09, return access denied. + * + * Returns Win32 error codes. + */ +/*ARGSUSED*/ +static int +srvsvc_s_NetNameValidate(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetNameValidate *param = arg; + + switch (param->type) { + case 0x09: + param->status = lmshare_is_valid((char *)param->pathname) ? + ERROR_SUCCESS : ERROR_INVALID_NAME; + break; + + default: + param->status = ERROR_ACCESS_DENIED; + break; + } + + return (MLRPC_DRC_OK); +} + +/* + * srvsvc_s_NetShareAdd + * + * Add a new share. We support info levels 2 and 502 but ignore the + * security descriptor in level 502 requests. Only the administrator, + * or a member of the domain administrators group, is allowed to add + * shares. + * + * This interface is used by the rmtshare command from the NT resource + * kit. Rmtshare allows a client to add or remove shares on a server + * from the client's command line. + * + * Note that we don't support security descriptors on a share. If the + * /grant is used, the share will be created but the subsequent attempt + * to manipulate the security descriptor (NetShareGetInfo) will fail. + * Similarly for the /remove option. + * + * Returns Win32 error codes. + */ +static int +srvsvc_s_NetShareAdd(void *arg, struct mlrpc_xaction *mxa) +{ + static DWORD parm_err = 0; + DWORD parm_stat; + struct mslm_NetShareAdd *param = arg; + smb_dr_user_ctx_t *user_ctx; + struct mslm_SHARE_INFO_2 *info2; + struct lmshare_info si; + char realpath[MAXPATHLEN]; + + user_ctx = mxa->context->user_ctx; + + if (srvsvc_is_poweruser(mxa) == 0) { + bzero(param, sizeof (struct mslm_NetShareAdd)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); + } + + switch (param->level) { + case 2: + info2 = param->info.un.info2; + break; + + case 502: + info2 = (struct mslm_SHARE_INFO_2 *)param->info.un.info502; + break; + + default: + bzero(param, sizeof (struct mslm_NetShareAdd)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); + } + + if (info2->shi2_netname == 0 || info2->shi2_path == 0) { + bzero(param, sizeof (struct mslm_NetShareAdd)); + param->status = NERR_NetNameNotFound; + return (MLRPC_DRC_OK); + } + + if (lmshare_is_restricted((char *)info2->shi2_netname)) { + bzero(param, sizeof (struct mslm_NetShareAdd)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); + } + + if (info2->shi2_remark == 0) + info2->shi2_remark = (unsigned char *)""; + + /* + * Derive the real path which will be stored in the + * directory field of the lmshare_info_t structure + * from the path field in this RPC request. + */ + parm_stat = lmshare_get_realpath((const char *)info2->shi2_path, + realpath, MAXPATHLEN); + + if (parm_stat != NERR_Success) { + bzero(param, sizeof (struct mslm_NetShareAdd)); + param->status = parm_stat; + param->parm_err + = (user_ctx->du_native_os == NATIVE_OS_WIN95) ? + 0 : &parm_err; + return (MLRPC_DRC_OK); + } + + (void) memset(&si, 0, sizeof (lmshare_info_t)); + (void) strlcpy(si.share_name, (const char *)info2->shi2_netname, + MAXNAMELEN); + + (void) strlcpy(si.directory, realpath, MAXPATHLEN); + (void) strlcpy(si.comment, (const char *)info2->shi2_remark, + LMSHR_COMMENT_MAX); + + si.mode = LMSHRM_PERM; + + param->status = lmshare_add(&si, 1); + param->parm_err = (user_ctx->du_native_os == NATIVE_OS_WIN95) ? + 0 : &parm_err; + return (MLRPC_DRC_OK); +} + +/* + * srvsvc_is_poweruser + * + * Check whether or not the specified user has power-user privileges, + * i.e. is a member of the Domain Admins, Administrators or Power + * Users groups. This is typically required for operations such as + * adding/deleting shares. + * + * Returns 1 if the user is a power user, otherwise returns 0. + */ +static int +srvsvc_is_poweruser(struct mlrpc_xaction *mxa) +{ + smb_dr_user_ctx_t *user = mxa->context->user_ctx; + + return ((user->du_flags & SMB_ATF_ADMIN) || + (user->du_flags & SMB_ATF_POWERUSER)); +} + +/* + * srvsvc_s_NetShareEnum + * + * Request for various levels of information about our shares. + * Level 0: just the share names. + * Level 1: the share name, the share type and the comment field. + * Level 2: everything that we know about the shares. + */ +static int +srvsvc_s_NetShareEnum(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetShareEnum *param = arg; + struct mslm_infonres *infonres; + DWORD status; + DWORD n_shares; + + infonres = MLRPC_HEAP_NEW(mxa, struct mslm_infonres); + if (infonres == 0) { + bzero(param, sizeof (struct mslm_NetShareEnum)); + param->status = ERROR_NOT_ENOUGH_MEMORY; + return (MLRPC_DRC_OK); + } + + infonres->entriesread = 0; + infonres->entries = 0; + param->result.level = param->level; + param->result.bufptr.p = infonres; + param->totalentries = 1; /* NT stream hint value: prefmaxlen? */ + param->status = ERROR_SUCCESS; + + n_shares = lmshare_num_shares(); + + switch (param->level) { + case 0: + status = mlsvc_NetShareEnumLevel0(infonres, n_shares, mxa, 0); + break; + + case 1: + status = mlsvc_NetShareEnumLevel1(infonres, n_shares, mxa, 0); + break; + + case 2: + status = mlsvc_NetShareEnumLevel2(infonres, n_shares, mxa, 0); + break; + + case 502: + status = mlsvc_NetShareEnumLevel502(infonres, n_shares, mxa, 0); + break; + + default: + status = ERROR_INVALID_PARAMETER; + break; + } + + if (status != 0) { + bzero(param, sizeof (struct mslm_NetShareEnum)); + param->status = status; + return (MLRPC_DRC_OK); + } + + param->resume_handle = 0; + param->totalentries = infonres->entriesread; + param->status = status; + return (MLRPC_DRC_OK); +} + + +/* + * srvsvc_s_NetShareEnumSticky + * + * Request for various levels of information about our shares. + * Level 0: just the share names. + * Level 1: the share name, the share type and the comment field. + * Level 2: everything that we know about the shares. + * + * NetShareEnumSticky is the same as NetShareEnum except that hidden + * shares are not returned. This call was apparently added due to a + * bug in the NT implementation of NetShareEnum - it didn't process + * the resume handle correctly so that attempts to enumerate large + * share lists resulted in an infinite loop. + */ +static int +srvsvc_s_NetShareEnumSticky(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetShareEnum *param = arg; + struct mslm_infonres *infonres; + DWORD resume_handle; + DWORD status; + DWORD n_shares; + + infonres = MLRPC_HEAP_NEW(mxa, struct mslm_infonres); + if (infonres == 0) { + bzero(param, sizeof (struct mslm_NetShareEnum)); + param->status = ERROR_NOT_ENOUGH_MEMORY; + return (MLRPC_DRC_OK); + } + + infonres->entriesread = 0; + infonres->entries = 0; + param->result.level = param->level; + param->result.bufptr.p = infonres; + param->totalentries = 1; /* NT stream hint value: prefmaxlen? */ + param->status = ERROR_SUCCESS; + + n_shares = lmshare_num_shares(); + + if (param->resume_handle) + resume_handle = *param->resume_handle; + else + resume_handle = 0; + + switch (param->level) { + case 0: + status = mlsvc_NetShareEnumLevel0(infonres, n_shares, mxa, 1); + break; + + case 1: + status = mlsvc_NetShareEnumLevel1(infonres, n_shares, mxa, 1); + break; + + case 2: + status = mlsvc_NetShareEnumLevel2(infonres, n_shares, mxa, 1); + break; + + case 502: + status = mlsvc_NetShareEnumLevel502(infonres, n_shares, mxa, 1); + break; + + default: + status = ERROR_INVALID_PARAMETER; + break; + } + + if (status != 0) { + bzero(param, sizeof (struct mslm_NetShareEnum)); + param->status = status; + return (MLRPC_DRC_OK); + } + + if (param->resume_handle) + *param->resume_handle = resume_handle; + param->totalentries = infonres->entriesread; + param->status = status; + return (MLRPC_DRC_OK); +} + + + +/* + * mlsvc_NetShareEnumLevel0 + * + * Build the level 0 share information. The list should have been built + * before we got here so all we have to do is copy the share names to + * the response heap and setup the infonres values. + */ +static DWORD +mlsvc_NetShareEnumLevel0(struct mslm_infonres *infonres, DWORD n_shares, + struct mlrpc_xaction *mxa, char sticky) +{ + struct mslm_SHARE_INFO_0 *info0; + lmshare_iterator_t *iterator; + lmshare_info_t *si; + DWORD i; + DWORD status; + smb_dr_user_ctx_t *user_ctx = mxa->context->user_ctx; + + info0 = MLRPC_HEAP_NEWN(mxa, struct mslm_SHARE_INFO_0, n_shares); + if (info0 == 0) { + status = ERROR_NOT_ENOUGH_MEMORY; + return (status); + } + + iterator = lmshare_open_iterator(LMSHRM_ALL); + if (iterator == NULL) { + status = ERROR_NOT_ENOUGH_MEMORY; + return (status); + } + + i = 0; + while ((si = lmshare_iterate(iterator)) != 0) { + if (sticky && (si->stype & STYPE_SPECIAL)) + continue; + + if (smb_is_autohome(si)) + continue; + + status = mlsvc_NetShareEnumCommon(mxa, i, 0, si, + (void *)info0); + + if (status != ERROR_SUCCESS) + break; + + i++; + } + + i = srvsvc_add_autohome(mxa, user_ctx->du_account, i, 0, (char *)info0); + + lmshare_close_iterator(iterator); + + infonres->entriesread = i; + infonres->entries = info0; + return (ERROR_SUCCESS); +} + + +/* + * mlsvc_NetShareEnumLevel1 + * + * Build the level 1 share information. The list should have been built + * before we arrived here so all we have to do is copy the share info + * to the response heap and setup the infonres values. The only thing + * to be aware of here is that there are minor difference between the + * various share types. + */ +static DWORD +mlsvc_NetShareEnumLevel1(struct mslm_infonres *infonres, DWORD n_shares, + struct mlrpc_xaction *mxa, char sticky) +{ + struct mslm_SHARE_INFO_1 *info1; + lmshare_iterator_t *iterator; + lmshare_info_t *si; + DWORD i; + smb_dr_user_ctx_t *user_ctx = mxa->context->user_ctx; + + info1 = MLRPC_HEAP_NEWN(mxa, struct mslm_SHARE_INFO_1, n_shares); + if (info1 == 0) + return (ERROR_NOT_ENOUGH_MEMORY); + + iterator = lmshare_open_iterator(LMSHRM_ALL); + if (iterator == NULL) + return (ERROR_NOT_ENOUGH_MEMORY); + + i = 0; + while ((si = lmshare_iterate(iterator)) != 0) { + if (sticky && (si->stype & STYPE_SPECIAL)) + continue; + + if (smb_is_autohome(si)) + continue; + + if (mlsvc_NetShareEnumCommon(mxa, i, 1, si, + (void *)info1) != ERROR_SUCCESS) + break; + i++; + } + + i = srvsvc_add_autohome(mxa, user_ctx->du_account, i, 1, (char *)info1); + + lmshare_close_iterator(iterator); + + infonres->entriesread = i; + infonres->entries = info1; + return (ERROR_SUCCESS); +} + +/* + * mlsvc_NetShareEnumLevel2 + * + * Build the level 2 share information. The list should have been built + * before we arrived here so all we have to do is copy the share info + * to the response heap and setup the infonres values. The only thing + * to be aware of here is that there are minor difference between the + * various share types. + */ +static DWORD +mlsvc_NetShareEnumLevel2(struct mslm_infonres *infonres, DWORD n_shares, + struct mlrpc_xaction *mxa, char sticky) +{ + struct mslm_SHARE_INFO_2 *info2; + lmshare_iterator_t *iterator; + lmshare_info_t *si; + DWORD i; + smb_dr_user_ctx_t *user_ctx = mxa->context->user_ctx; + + info2 = MLRPC_HEAP_NEWN(mxa, struct mslm_SHARE_INFO_2, n_shares); + if (info2 == 0) + return (ERROR_NOT_ENOUGH_MEMORY); + + iterator = lmshare_open_iterator(LMSHRM_ALL); + if (iterator == NULL) + return (ERROR_NOT_ENOUGH_MEMORY); + + i = 0; + while ((si = lmshare_iterate(iterator)) != 0) { + if (sticky && (si->stype & STYPE_SPECIAL)) + continue; + + if (smb_is_autohome(si)) + continue; + + if (mlsvc_NetShareEnumCommon(mxa, i, 2, si, + (void *)info2) != ERROR_SUCCESS) + break; + i++; + } + + i = srvsvc_add_autohome(mxa, user_ctx->du_account, i, 2, (char *)info2); + + lmshare_close_iterator(iterator); + infonres->entriesread = i; + infonres->entries = info2; + return (ERROR_SUCCESS); +} + +/* + * mlsvc_NetShareEnumLevel502 + * + * Build the level 502 share information. This is the same as level 2 + * but with a security descriptor in the share structure. We don't + * support SD's on shares so we can just set that field to zero. See + * mlsvc_NetShareEnumLevel2 for more information. + */ +static DWORD +mlsvc_NetShareEnumLevel502(struct mslm_infonres *infonres, DWORD n_shares, + struct mlrpc_xaction *mxa, char sticky) +{ + struct mslm_SHARE_INFO_502 *info502; + lmshare_iterator_t *iterator; + lmshare_info_t *si; + DWORD i; + smb_dr_user_ctx_t *user_ctx = mxa->context->user_ctx; + + info502 = MLRPC_HEAP_NEWN(mxa, struct mslm_SHARE_INFO_502, n_shares); + + if (info502 == 0) + return (ERROR_NOT_ENOUGH_MEMORY); + + iterator = lmshare_open_iterator(LMSHRM_ALL); + if (iterator == NULL) + return (ERROR_NOT_ENOUGH_MEMORY); + + i = 0; + while ((si = lmshare_iterate(iterator)) != 0) { + if (sticky && (si->stype & STYPE_SPECIAL)) + continue; + + if (smb_is_autohome(si)) + continue; + + if (mlsvc_NetShareEnumCommon( + mxa, i, 502, si, (void *)info502) != ERROR_SUCCESS) + break; + i++; + } + + i = srvsvc_add_autohome(mxa, user_ctx->du_account, i, 502, + (char *)info502); + + lmshare_close_iterator(iterator); + infonres->entriesread = i; + infonres->entries = info502; + return (ERROR_SUCCESS); +} + +/* + * mlsvc_NetShareEnumCommon + * + * Build the levels 0, 1, 2 and 502 share information. This function + * is called by the various NetShareEnum levels for each share. If + * we cannot build the share data for some reason, we return an error + * but the actual value of the error is not important to the caller. + * The caller just needs to know not to include this info in the RPC + * response. + * + * Returns: + * ERROR_SUCCESS + * ERROR_NOT_ENOUGH_MEMORY + * ERROR_INVALID_LEVEL + */ +static DWORD +mlsvc_NetShareEnumCommon(struct mlrpc_xaction *mxa, DWORD i, int level, + lmshare_info_t *si, void *infop) +{ + struct mslm_SHARE_INFO_0 *info0; + struct mslm_SHARE_INFO_1 *info1; + struct mslm_SHARE_INFO_2 *info2; + struct mslm_SHARE_INFO_502 *info502; + char shr_comment[LMSHR_COMMENT_MAX]; + + if ((si->stype & STYPE_MASK) == STYPE_IPC) { + /* + * Windows clients don't send the \\PIPE path for IPC$. + */ + si->directory[0] = '\0'; + (void) strcpy(si->comment, "Remote IPC"); + } + + if (si->comment && strlen(si->comment)) + (void) snprintf(shr_comment, sizeof (shr_comment), "%s (%s)", + si->directory, si->comment); + else + (void) strcpy(shr_comment, si->directory); + + switch (level) { + case 0: + info0 = (struct mslm_SHARE_INFO_0 *)infop; + info0[i].shi0_netname + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, si->share_name); + + if (info0[i].shi0_netname == 0) + return (ERROR_NOT_ENOUGH_MEMORY); + break; + + case 1: + info1 = (struct mslm_SHARE_INFO_1 *)infop; + info1[i].shi1_netname + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, si->share_name); + + info1[i].shi1_remark + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, shr_comment); + + info1[i].shi1_type = si->stype; + + if (!info1[i].shi1_netname || !info1[i].shi1_remark) + return (ERROR_NOT_ENOUGH_MEMORY); + break; + + case 2: + info2 = (struct mslm_SHARE_INFO_2 *)infop; + info2[i].shi2_netname + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, si->share_name); + + info2[i].shi2_remark + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, shr_comment); + + info2[i].shi2_path + = (unsigned char *)srvsvc_share_mkpath(mxa, si->directory); + + info2[i].shi2_type = si->stype; + info2[i].shi2_permissions = 0; + info2[i].shi2_max_uses = SHI_USES_UNLIMITED; + info2[i].shi2_current_uses = 0; + info2[i].shi2_passwd + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, empty_string); + + if (!info2[i].shi2_netname || !info2[i].shi2_remark || + !info2[i].shi2_passwd || !info2[i].shi2_path) + return (ERROR_NOT_ENOUGH_MEMORY); + + break; + + case 502: + info502 = (struct mslm_SHARE_INFO_502 *)infop; + info502[i].shi502_netname + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, si->share_name); + + info502[i].shi502_remark + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, shr_comment); + + info502[i].shi502_path + = (unsigned char *)srvsvc_share_mkpath(mxa, si->directory); + + info502[i].shi502_type = si->stype; + info502[i].shi502_permissions = 0; + info502[i].shi502_max_uses = SHI_USES_UNLIMITED; + info502[i].shi502_current_uses = 0; + info502[i].shi502_passwd + = (unsigned char *)MLRPC_HEAP_STRSAVE(mxa, empty_string); + + info502[i].shi502_reserved = 0; + info502[i].shi502_security_descriptor = 0; + + if (!info502[i].shi502_netname || !info502[i].shi502_remark || + !info502[i].shi502_passwd || !info502[i].shi502_path) + return (ERROR_NOT_ENOUGH_MEMORY); + break; + + default: + return (ERROR_INVALID_LEVEL); + } + + return (ERROR_SUCCESS); +} + +/* + * srvsvc_s_NetShareDel + * + * Delete a share. Only the administrator, or a member of the domain + * administrators group, is allowed to delete shares. + * + * This interface is used by the rmtshare command from the NT resource + * kit. Rmtshare allows a client to add or remove shares on a server + * from the client's command line. + * + * Returns Win32 error codes. + */ +static int +srvsvc_s_NetShareDel(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetShareDel *param = arg; + + if (srvsvc_is_poweruser(mxa) == 0 || + lmshare_is_restricted((char *)param->netname)) { + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); + } + + param->status = lmshare_delete((char *)param->netname, 1); + return (MLRPC_DRC_OK); +} + +/* + * srvsvc_s_NetGetFileSecurity + * + * Get security descriptor of the requested file/folder + * + * Right now, just returns ERROR_ACCESS_DENIED, because we cannot + * get the requested SD here in MLRPC code. + */ +/*ARGSUSED*/ +static int +srvsvc_s_NetGetFileSecurity(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetGetFileSecurity *param = arg; + + param->length = 0; + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * srvsvc_s_NetSetFileSecurity + * + * Set the given security descriptor for the requested file/folder + * + * Right now, just returns ERROR_ACCESS_DENIED, because we cannot + * set the requested SD here in MLRPC code. + */ +/*ARGSUSED*/ +static int +srvsvc_s_NetSetFileSecurity(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetSetFileSecurity *param = arg; + + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +static mlrpc_stub_table_t srvsvc_stub_table[] = { + { srvsvc_s_NetConnectEnum, SRVSVC_OPNUM_NetConnectEnum }, + { srvsvc_s_NetFileEnum, SRVSVC_OPNUM_NetFileEnum }, + { srvsvc_s_NetFileClose, SRVSVC_OPNUM_NetFileClose }, + { srvsvc_s_NetShareGetInfo, SRVSVC_OPNUM_NetShareGetInfo }, + { srvsvc_s_NetShareSetInfo, SRVSVC_OPNUM_NetShareSetInfo }, + { srvsvc_s_NetSessionEnum, SRVSVC_OPNUM_NetSessionEnum }, + { srvsvc_s_NetSessionDel, SRVSVC_OPNUM_NetSessionDel }, + { srvsvc_s_NetServerGetInfo, SRVSVC_OPNUM_NetServerGetInfo }, + { srvsvc_s_NetRemoteTOD, SRVSVC_OPNUM_NetRemoteTOD }, + { srvsvc_s_NetNameValidate, SRVSVC_OPNUM_NetNameValidate }, + { srvsvc_s_NetShareAdd, SRVSVC_OPNUM_NetShareAdd }, + { srvsvc_s_NetShareDel, SRVSVC_OPNUM_NetShareDel }, + { srvsvc_s_NetShareEnum, SRVSVC_OPNUM_NetShareEnum }, + { srvsvc_s_NetShareEnumSticky, SRVSVC_OPNUM_NetShareEnumSticky }, + { srvsvc_s_NetGetFileSecurity, SRVSVC_OPNUM_NetGetFileSecurity }, + { srvsvc_s_NetSetFileSecurity, SRVSVC_OPNUM_NetSetFileSecurity }, + {0} +}; diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_svcctl.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_svcctl.c new file mode 100644 index 0000000000..eca831f899 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_svcctl.c @@ -0,0 +1,485 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * NT Service Control Services (SVCCTL) RPC interface definition. + * This interface provides remote access to add, remove, start and + * stop services. + */ + +#include <stdio.h> +#include <strings.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/nterror.h> +#include <smbsrv/nmpipes.h> +#include <smbsrv/winsvc.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/svcctl.ndl> + +/* + * SVCCTL diagnostics flag: set to 1 to enable verbose logging. + */ +int svcctl_debug = 0; + +/* + * The handle keys for the various types of handles returned + * by this interface. + */ +#define SVCCTL_MANAGER_KEY "svcctlManager" +#define SVCCTL_SERVICE_KEY "svcctlService" + + +typedef struct { + char *svc_name; + char *display_name; + char *local_name; +} svc_info_t; + + +/* + * The list of service we report to Server Manager. Entries don't + * have to be alphabetically arranged here; Server Manager will + * sort the list. + * + * NOTE: The enumeration list is currently built in a fixed-size, + * 1024 byte buffer. Be careful not to over-run the buffer. + */ +static svc_info_t svc_info[] = { + { "Dhcp", "DHCP Client", "dhcpc" }, + { "EventLog", "EventLog", NULL }, + { "Netlogon", "Net Logon", NULL }, + { "WebAdmin", "Web Administration", "httpd" }, + { "RlgnSvr", "Remote Login", "rlogin" }, + { "RpcSs", "Remote Procedure Call (RPC) Service", NULL }, + { "RshSvr", "Remote Shell", "rsh" }, + { "SshSvr", "Secure Shell", "ssh" }, + { "TlntSvr", "Telnet", "telnet" }, + { "Dnscache", "DNS Client", "dns" }, + { "NisSvr", "Network Information Services", NULL }, + { "NtLmSsp", "NT LM Security Support Provider", NULL }, + { "Samss", "Security Accounts Manager", NULL }, + { "UPS", "Uninterruptible Power Supply", "ups" }, + { "TftpSvr", "TFTP", "tftp" } +}; + +#define SVCCTL_NUM_SVCS (sizeof (svc_info)/sizeof (svc_info[0])) + + +static DWORD svcctl_get_status(const char *); +static DWORD svcctl_validate_service(char *); +static DWORD svcctl_validate_handle(char *, ms_handle_t *, char *, + struct mlrpc_xaction *); +static int svcctl_is_admin(struct mlrpc_xaction *); + +static int svcctl_s_Close(void *, struct mlrpc_xaction *); +static int svcctl_s_OpenManager(void *, struct mlrpc_xaction *); +static int svcctl_s_OpenService(void *, struct mlrpc_xaction *); +static int svcctl_s_QueryServiceStatus(void *, struct mlrpc_xaction *); +static int svcctl_s_QueryServiceConfig(void *, struct mlrpc_xaction *); +static int svcctl_s_EnumServicesStatus(void *, struct mlrpc_xaction *); + +static mlrpc_stub_table_t svcctl_stub_table[] = { + { svcctl_s_Close, SVCCTL_OPNUM_Close }, + { svcctl_s_OpenManager, SVCCTL_OPNUM_OpenManager }, + { svcctl_s_OpenService, SVCCTL_OPNUM_OpenService }, + { svcctl_s_QueryServiceStatus, SVCCTL_OPNUM_QueryServiceStatus }, + { svcctl_s_QueryServiceConfig, SVCCTL_OPNUM_QueryServiceConfig }, + { svcctl_s_EnumServicesStatus, SVCCTL_OPNUM_EnumServicesStatus }, + {0} +}; + +static mlrpc_service_t svcctl_service = { + "SVCCTL", /* name */ + "Service Control Services", /* desc */ + "\\svcctl", /* endpoint */ + PIPE_NTSVCS, /* sec_addr_port */ + "367abb81-9844-35f1-ad3298f038001003", 2, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(svcctl_interface), /* interface ti */ + svcctl_stub_table /* stub_table */ +}; + +/* + * svcctl_initialize + * + * This function registers the SVCCTL RPC interface with the RPC runtime + * library. It must be called in order to use either the client side + * or the server side functions. + */ +void +svcctl_initialize(void) +{ + (void) mlrpc_register_service(&svcctl_service); +} + +/* + * svcctl_s_Close + * + * This is a request to close the SVCCTL interface specified by the + * handle. Free the handle and zero out the result handle for the + * client. + * + * Returns: + * ERROR_SUCCESS + * ERROR_INVALID_HANDLE + */ +static int +svcctl_s_Close(void *arg, struct mlrpc_xaction *mxa) +{ + struct svcctl_Close *param = arg; + smb_dr_user_ctx_t *user_ctx = mxa->context->user_ctx; + ms_handle_t *handle; + DWORD status; + + if (svcctl_debug) + smb_token_log(LOG_DEBUG, user_ctx, "(SvcctlClose)"); + + handle = (ms_handle_t *)¶m->handle; + status = svcctl_validate_handle("SvcctlClose", handle, 0, mxa); + + if (status == ERROR_SUCCESS) + (void) mlsvc_put_handle((ms_handle_t *)¶m->handle); + + bzero(¶m->result_handle, sizeof (svcctl_handle_t)); + param->status = status; + return (MLRPC_DRC_OK); +} + +/* + * svcctl_s_OpenManager + * + * This is a request to open the service control manager. Dependent + * on the desired access we either generate a handle to be used on + * subsequent requests or deny access. + * + * Returns: + * ERROR_SUCCESS + * ERROR_ACCESS_DENIED + * + * Return a handle for use with subsequent svcctl requests. + */ +static int +svcctl_s_OpenManager(void *arg, struct mlrpc_xaction *mxa) +{ + struct svcctl_OpenManager *param = arg; + ms_handle_t *handle; + int rc; + + rc = svcctl_is_admin(mxa); + + if ((rc == 0) || (param->desired_access & SC_MANAGER_LOCK) != 0) { + /* + * The user doesn't have Administrator rights + * or wants a write lock on the Services DB. + */ + bzero(¶m->handle, sizeof (svcctl_handle_t)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); + } + + handle = mlsvc_get_handle(MLSVC_IFSPEC_SVCCTL, SVCCTL_MANAGER_KEY, 0); + bcopy(handle, ¶m->handle, sizeof (svcctl_handle_t)); + + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * svcctl_s_OpenService + * + * Return a handle for use with subsequent svcctl requests. + * + * Returns: + * ERROR_SUCCESS + * ERROR_INVALID_HANDLE + * ERROR_SERVICE_DOES_NOT_EXIST + */ +static int +svcctl_s_OpenService(void *arg, struct mlrpc_xaction *mxa) +{ + struct svcctl_OpenService *param = arg; + ms_handle_t *handle; + DWORD status; + + status = svcctl_validate_handle("SvcctlOpenService", + (ms_handle_t *)¶m->manager_handle, SVCCTL_MANAGER_KEY, mxa); + + if (status != ERROR_SUCCESS) { + bzero(¶m->service_handle, sizeof (svcctl_handle_t)); + param->status = status; + return (MLRPC_DRC_OK); + } + + status = svcctl_validate_service((char *)param->service_name); + if (status != ERROR_SUCCESS) { + bzero(¶m->service_handle, sizeof (svcctl_handle_t)); + param->status = status; + return (MLRPC_DRC_OK); + } + + handle = mlsvc_get_handle(MLSVC_IFSPEC_SVCCTL, SVCCTL_SERVICE_KEY, 0); + bcopy(handle, ¶m->service_handle, sizeof (svcctl_handle_t)); + + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * svcctl_s_QueryServiceStatus + * + * Returns: + * ERROR_SUCCESS + * ERROR_INVALID_HANDLE + */ +static int +svcctl_s_QueryServiceStatus(void *arg, struct mlrpc_xaction *mxa) +{ + struct svcctl_QueryServiceStatus *param = arg; + DWORD status; + + status = svcctl_validate_handle("SvcctlQueryServiceStatus", + (ms_handle_t *)¶m->service_handle, SVCCTL_SERVICE_KEY, mxa); + + if (status != ERROR_SUCCESS) { + bzero(¶m, sizeof (struct svcctl_QueryServiceStatus)); + param->status = status; + return (MLRPC_DRC_OK); + } + + param->service_status.service_type = SERVICE_WIN32_SHARE_PROCESS; + param->service_status.cur_state = SERVICE_RUNNING; + param->service_status.ctrl_accepted = 0; + param->service_status.w32_exitcode = 0; + param->service_status.svc_specified_exitcode = 0; + param->service_status.check_point = 0; + param->service_status.wait_hint = 0; + + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * svcctl_s_EnumServicesStatus + * + * Enumerate the list of services we support. Currently, this list + * is built in a fixed-size 1024 byte buffer - be careful not to + * over-run the buffer. + * + * Returns: + * ERROR_SUCCESS + * ERROR_INVALID_HANDLE + */ +static int +svcctl_s_EnumServicesStatus(void *arg, struct mlrpc_xaction *mxa) +{ + struct svcctl_EnumServicesStatus *param = arg; + svc_enum_status_t *service_table; + svc_enum_status_t *svc; + mts_wchar_t *wide_name; + char *name; + int i, namelen; + int offs; + DWORD status; + + status = svcctl_validate_handle("SvcctlEnumServicesStatus", + (ms_handle_t *)¶m->manager_handle, SVCCTL_MANAGER_KEY, mxa); + + if (status != ERROR_SUCCESS) { + param->status = status; + return (MLRPC_DRC_OK); + } + + if (param->buf_size < 1024) { + param->status = ERROR_MORE_DATA; + return (MLRPC_DRC_OK); + } + + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + service_table = (svc_enum_status_t *)param->services; + offs = SVCCTL_NUM_SVCS * sizeof (svc_enum_status_t); + + for (i = 0; i < SVCCTL_NUM_SVCS; i++) { + svc = &service_table[i]; + + svc->svc_name = offs; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + wide_name = (mts_wchar_t *)¶m->services[offs]; + name = svc_info[i].svc_name; + namelen = strlen(name) + 1; + (void) mts_mbstowcs(wide_name, name, namelen); + + offs += namelen * 2; + + svc->display_name = offs; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + wide_name = (mts_wchar_t *)¶m->services[offs]; + name = svc_info[i].display_name; + namelen = strlen(name) + 1; + (void) mts_mbstowcs(wide_name, name, namelen); + + offs += namelen * 2; + + name = svc_info[i].local_name; + if (name) + svc->svc_status.cur_state = svcctl_get_status(name); + else + svc->svc_status.cur_state = SERVICE_RUNNING; + + svc->svc_status.service_type = SERVICE_WIN32_SHARE_PROCESS; + svc->svc_status.ctrl_accepted = 0; + svc->svc_status.w32_exitcode = 0; + svc->svc_status.svc_specified_exitcode = 0; + svc->svc_status.check_point = 0; + svc->svc_status.wait_hint = 0; + } + + param->buf_size = 1024; + param->bytes_needed = 0; + param->svc_num = SVCCTL_NUM_SVCS; + param->resume_handle = 0; + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * svcctl_s_QueryServiceConfig + * + * Returns: + * ERROR_SUCCESS + * ERROR_INVALID_HANDLE + */ +static int +svcctl_s_QueryServiceConfig(void *arg, struct mlrpc_xaction *mxa) +{ + struct svcctl_QueryServiceConfig *param = arg; + DWORD status; + + status = svcctl_validate_handle("SvcctlQueryServiceConfig", + (ms_handle_t *)¶m->service_handle, SVCCTL_SERVICE_KEY, mxa); + + if (status != ERROR_SUCCESS) { + bzero(¶m, sizeof (struct svcctl_QueryServiceConfig)); + param->status = status; + return (MLRPC_DRC_OK); + } + + param->service_cfg.service_type = SERVICE_WIN32_SHARE_PROCESS; + param->service_cfg.start_type = SERVICE_AUTO_START; + param->service_cfg.error_control = SERVICE_ERROR_IGNORE; + param->service_cfg.binary_pathname = 0; + param->service_cfg.loadorder_group = 0; + param->service_cfg.tag_id = 0; + param->service_cfg.dependencies = 0; + param->service_cfg.service_startname = 0; + param->service_cfg.display_name = 0; + + param->cfg_bytes = sizeof (svc_config_t); + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * Check to see whether or not a service is supported. The check is + * case-insensitive to avoid any naming issues due to the different + * versions of Windows. + * + * Returns: + * ERROR_SUCCESS + * ERROR_SERVICE_DOES_NOT_EXIST + */ +static DWORD +svcctl_validate_service(char *svc_name) +{ + int i; + + if (svc_name == NULL) + return (ERROR_SERVICE_DOES_NOT_EXIST); + + for (i = 0; i < SVCCTL_NUM_SVCS; i++) { + if (strcasecmp(svc_name, svc_info[i].svc_name) == 0) + return (ERROR_SUCCESS); + } + + return (ERROR_SERVICE_DOES_NOT_EXIST); +} + +/* + * Check whether or not the svcctl module allocated this handle. + * + * Returns: + * ERROR_SUCCESS + * ERROR_INVALID_HANDLE. + */ +/*ARGSUSED*/ +static DWORD +svcctl_validate_handle(char *name, ms_handle_t *handle, char *key, + struct mlrpc_xaction *mxa) +{ + int mgr_erc; + int svc_erc; + + mgr_erc = mlsvc_validate_handle(handle, SVCCTL_MANAGER_KEY); + svc_erc = mlsvc_validate_handle(handle, SVCCTL_SERVICE_KEY); + + if (mgr_erc == 0 && svc_erc == 0) + return (ERROR_INVALID_HANDLE); + + return (ERROR_SUCCESS); +} + +/* + * Report the service status: SERVICE_PAUSED or SERVICE_RUNNING. + */ +/*ARGSUSED*/ +static DWORD +svcctl_get_status(const char *name) +{ + return (SERVICE_RUNNING); +} + +/* + * SVCCTL access is restricted to administrators: members of + * the Domain Admins or Administrators groups. + * + * Returns 1 if the user has admin rights. Otherwise returns 0. + */ +static int +svcctl_is_admin(struct mlrpc_xaction *mxa) +{ + smb_dr_user_ctx_t *user_ctx = mxa->context->user_ctx; + + if (user_ctx == NULL) + return (0); + + return (user_ctx->du_flags & SMB_ATF_ADMIN); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c new file mode 100644 index 0000000000..29cd3f058a --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_util.c @@ -0,0 +1,752 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Utility functions to support the RPC interface library. + */ + +#include <stdio.h> +#include <stdarg.h> +#include <strings.h> +#include <unistd.h> +#include <netdb.h> +#include <stdlib.h> +#include <pwd.h> +#include <grp.h> + +#include <sys/time.h> +#include <sys/systm.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libsmbrdr.h> +#include <smbsrv/libsmbns.h> +#include <smbsrv/libmlsvc.h> + +#include <smbsrv/smbinfo.h> +#include <smbsrv/ntsid.h> +#include <smbsrv/lsalib.h> +#include <smbsrv/samlib.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/mlsvc.h> + +extern int netr_open(char *, char *, mlsvc_handle_t *); +extern int netr_close(mlsvc_handle_t *); +extern DWORD netlogon_auth(char *, mlsvc_handle_t *, DWORD); +extern int mlsvc_user_getauth(char *, char *, smb_auth_info_t *); + +static int mlsvc_lookup_local_name(char *name, nt_sid_t **sid); +static int mlsvc_lookup_nt_name(char *name, nt_sid_t **sid); +static int mlsvc_lookup_nt_sid(nt_sid_t *sid, char *buf, int bufsize); + +/* + * Compare the supplied domain name with the local hostname. + * We need to deal with both server names and fully-qualified + * domain names. + * + * Returns: + * 0 The specified domain is not the local domain, + * 1 The Specified domain is the local domain. + * -1 Invalid parameter or unable to get the local + * system information. + */ +int +mlsvc_is_local_domain(const char *domain) +{ + char hostname[MAXHOSTNAMELEN]; + uint32_t mode; + int rc; + + if (strchr(domain, '.') != NULL) + rc = smb_getfqhostname(hostname, MAXHOSTNAMELEN); + else + rc = smb_gethostname(hostname, MAXHOSTNAMELEN, 1); + + if (rc != 0) + return (-1); + + rc = strcasecmp(domain, hostname); + mode = smb_get_security_mode(); + + if ((rc == 0) || (mode == SMB_SECMODE_WORKGRP)) + return (1); + + return (0); +} + +/* + * mlsvc_lookup_name + * + * Lookup a name in the specified domain and translate it to a SID. + * If the name is in the NT domain, it may refer to a user, group or + * alias. Otherwise it must refer to a UNIX username. The memory for + * the sid is allocated using malloc so the caller should call free + * when it is no longer required. + * + * On success, 0 will be returned and sid will point to a local domain + * user SID. Otherwise -1 will be returned. + */ +int +mlsvc_lookup_name(char *domain, char *name, nt_sid_t **sid) +{ + if (domain == NULL || name == NULL || sid == NULL) + return (-1); + + if (mlsvc_is_local_domain(domain) == 1) + return (mlsvc_lookup_local_name(name, sid)); + else + return (mlsvc_lookup_nt_name(name, sid)); +} + +/* + * mlsvc_lookup_local_name + * + * Lookup a name in the local password file and translate it to a SID. + * The name must refer to a user. This is a private function intended + * to support mlsvc_lookup_name so it doesn't perform any parameter + * validation. The memory for the sid is allocated using malloc so the + * caller must call free when it is no longer required. + * + * On success, 0 will be returned and sid will point to a local domain + * user SID. Otherwise -1 will be returned. + */ +static int +mlsvc_lookup_local_name(char *name, nt_sid_t **sid) +{ + struct passwd *pw; + nt_sid_t *domain_sid; + + if ((pw = getpwnam(name)) == NULL) + return (-1); + + if ((domain_sid = nt_domain_local_sid()) == NULL) + return (-1); + + *sid = nt_sid_splice(domain_sid, pw->pw_uid); + return (0); +} + +/* + * mlsvc_lookup_nt_name + * + * Lookup a name in the specified NT domain and translate it to a SID. + * The name may refer to a user, group or alias. This is a private + * function intended to support mlsvc_lookup_name so it doesn't do any + * parameter validation. The memory for the sid is allocated using + * malloc so the caller should call free when it is no longer required. + * + * On success, 0 will be returned and sid will point to an NT domain + * user SID. Otherwise -1 will be returned. + */ +static int +mlsvc_lookup_nt_name(char *name, nt_sid_t **sid) +{ + smb_userinfo_t *user_info; + + if ((user_info = mlsvc_alloc_user_info()) == NULL) + return (-1); + + if (lsa_lookup_name(0, 0, name, user_info) != 0) + return (-1); + + *sid = nt_sid_splice(user_info->domain_sid, user_info->rid); + mlsvc_free_user_info(user_info); + return (0); +} + +/* + * mlsvc_lookup_sid + * + * Lookup a SID and translate it to a name. The name returned may refer + * to a domain, user, group or alias dependent on the SID. On success 0 + * will be returned. Otherwise -1 will be returned. + */ +int +mlsvc_lookup_sid(nt_sid_t *sid, char *buf, int bufsize) +{ + struct passwd *pw; + struct group *gr; + nt_group_t *grp; + DWORD rid; + + if (sid == NULL || buf == NULL) + return (-1); + + if (nt_sid_is_local(sid)) { + (void) nt_sid_get_rid(sid, &rid); + + switch (SAM_RID_TYPE(rid)) { + case SAM_RT_NT_UID: + break; + + case SAM_RT_NT_GID: + if ((grp = nt_groups_lookup_rid(rid)) == NULL) + return (-1); + + (void) strlcpy(buf, grp->name, bufsize); + break; + + case SAM_RT_UNIX_UID: + if ((pw = getpwuid(SAM_DECODE_RID(rid))) == NULL) + return (-1); + + (void) strlcpy(buf, pw->pw_name, bufsize); + break; + + case SAM_RT_UNIX_GID: + if ((gr = getgrgid(SAM_DECODE_RID(rid))) == NULL) + return (-1); + + (void) strlcpy(buf, gr->gr_name, bufsize); + break; + } + + return (0); + } + + return (mlsvc_lookup_nt_sid(sid, buf, bufsize)); +} + +/* + * mlsvc_lookup_nt_sid + * + * Lookup an NT SID and translate it to a name. This is a private + * function intended to support mlsvc_lookup_sid so it doesn't do any + * parameter validation. The input account_name specifies the logon/ + * session to be used for the lookup. It doesn't need to have any + * association with the SID being looked up. The name returned may + * refer to a domain, user, group or alias dependent on the SID. + * + * On success the name will be copied into buf and 0 will be returned. + * Otherwise -1 will be returned. + */ +static int +mlsvc_lookup_nt_sid(nt_sid_t *sid, char *buf, int bufsize) +{ + smb_userinfo_t *user_info; + int rc; + + if ((user_info = mlsvc_alloc_user_info()) == NULL) + return (-1); + + if ((rc = lsa_lookup_sid(sid, user_info)) == 0) + (void) strlcpy(buf, user_info->name, bufsize); + + mlsvc_free_user_info(user_info); + return (rc); +} + +/* + * mlsvc_alloc_user_info + * + * Allocate a user_info structure and set the contents to zero. A + * pointer to the user_info structure is returned. + */ +smb_userinfo_t * +mlsvc_alloc_user_info(void) +{ + smb_userinfo_t *user_info; + + user_info = (smb_userinfo_t *)malloc(sizeof (smb_userinfo_t)); + if (user_info == NULL) + return (NULL); + + bzero(user_info, sizeof (smb_userinfo_t)); + return (user_info); +} + +/* + * mlsvc_free_user_info + * + * Free a user_info structure. This function ensures that the contents + * of the user_info are freed as well as the user_info itself. + */ +void +mlsvc_free_user_info(smb_userinfo_t *user_info) +{ + if (user_info) { + mlsvc_release_user_info(user_info); + free(user_info); + } +} + +/* + * mlsvc_release_user_info + * + * Release the contents of a user_info structure and zero out the + * elements but do not free the user_info structure itself. This + * function cleans out the structure so that it can be reused without + * worrying about stale contents. + */ +void +mlsvc_release_user_info(smb_userinfo_t *user_info) +{ + int i; + + if (user_info == NULL) + return; + + free(user_info->name); + free(user_info->domain_sid); + free(user_info->domain_name); + free(user_info->groups); + + if (user_info->n_other_grps) { + for (i = 0; i < user_info->n_other_grps; i++) + free(user_info->other_grps[i].sid); + + free(user_info->other_grps); + } + + free(user_info->user_sid); + free(user_info->pgrp_sid); + bzero(user_info, sizeof (smb_userinfo_t)); +} + +/* + * mlsvc_setadmin_user_info + * + * Determines if the given user is the domain Administrator or a + * member of Domain Admins or Administrators group and set the + * user_info->flags accordingly. + */ +void +mlsvc_setadmin_user_info(smb_userinfo_t *user_info) +{ + nt_domain_t *domain; + nt_group_t *grp; + int i; + + if ((domain = nt_domain_lookupbytype(NT_DOMAIN_PRIMARY)) == NULL) + return; + + if (!nt_sid_is_equal((nt_sid_t *)user_info->domain_sid, domain->sid)) + return; + + if (user_info->rid == DOMAIN_USER_RID_ADMIN) + user_info->flags |= SMB_UINFO_FLAG_DADMIN; + else if (user_info->primary_group_rid == DOMAIN_GROUP_RID_ADMINS) + user_info->flags |= SMB_UINFO_FLAG_DADMIN; + else { + for (i = 0; i < user_info->n_groups; i++) + if (user_info->groups[i].rid == DOMAIN_GROUP_RID_ADMINS) + user_info->flags |= SMB_UINFO_FLAG_DADMIN; + } + + grp = nt_group_getinfo("Administrators", RWLOCK_READER); + if (grp) { + i = nt_group_is_member(grp, user_info->user_sid); + nt_group_putinfo(grp); + if (i) + user_info->flags |= SMB_UINFO_FLAG_LADMIN; + } +} + +/* + * mlsvc_string_save + * + * This is a convenience function to prepare strings for an RPC call. + * An ms_string_t is set up with the appropriate lengths and str is + * set up to point to a copy of the original string on the heap. The + * macro MLRPC_HEAP_STRSAVE is an alias for mlrpc_heap_strsave, which + * extends the heap and copies the string into the new area. + */ +int +mlsvc_string_save(ms_string_t *ms, char *str, struct mlrpc_xaction *mxa) +{ + int length; + char *p; + + if (ms == NULL || str == NULL || mxa == NULL) + return (0); + + /* + * Windows NT expects the name length to exclude the + * terminating wchar null but doesn't care whether or + * not the allosize includes it. Windows 2000 insists + * that both the length and the allosize include the + * wchar null. + */ + length = mts_wcequiv_strlen(str); + ms->allosize = length + sizeof (mts_wchar_t); + + if (mxa->context->user_ctx->du_native_os == NATIVE_OS_WIN2000) + ms->length = ms->allosize; + else + ms->length = length; + + if ((p = MLRPC_HEAP_STRSAVE(mxa, str)) == NULL) { + return (0); + } + + ms->str = (LPTSTR)p; + return (1); +} + +/* + * mlsvc_sid_save + * + * Expand the heap and copy the sid into the new area. + * Returns a pointer to the copy of the sid on the heap. + */ +nt_sid_t * +mlsvc_sid_save(nt_sid_t *sid, struct mlrpc_xaction *mxa) +{ + nt_sid_t *heap_sid; + unsigned size; + + if (sid == NULL) + return (NULL); + + size = nt_sid_length(sid); + + if ((heap_sid = (nt_sid_t *)MLRPC_HEAP_MALLOC(mxa, size)) == NULL) + return (0); + + bcopy(sid, heap_sid, size); + return (heap_sid); +} + +/* + * mlsvc_is_null_handle + * + * Check a handle against a null handle. Returns 1 if the handle is + * null. Otherwise returns 0. + */ +int +mlsvc_is_null_handle(mlsvc_handle_t *handle) +{ + static ms_handle_t zero_handle; + + if (handle == NULL || handle->context == NULL) + return (1); + + if (!memcmp(&handle->handle, &zero_handle, sizeof (ms_handle_t))) + return (1); + + return (0); +} + +/* + * mlsvc_validate_user + * + * Returns NT status codes. + */ +DWORD +mlsvc_validate_user(char *server, char *domain, char *plain_user, + char *plain_text) +{ + smb_auth_info_t auth; + smb_ntdomain_t *di; + int erc; + DWORD status; + mlsvc_handle_t netr_handle; + char machine_passwd[MLSVC_MACHINE_ACCT_PASSWD_MAX]; + + machine_passwd[0] = '\0'; + + /* + * Ensure that the domain name is uppercase. + */ + (void) utf8_strupr(domain); + + /* + * There is no point continuing if the domain information is + * not available. Wait for up to 10 seconds and then give up. + */ + if ((di = smb_getdomaininfo(10)) == 0) { + status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + return (status); + } + + if (strcasecmp(domain, di->domain) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + return (status); + } + + erc = mlsvc_user_logon(server, domain, plain_user, plain_text); + + if (erc == AUTH_USER_GRANT) { + int isenabled; + + smb_config_rdlock(); + isenabled = smb_config_getyorn(SMB_CI_ADS_ENABLE); + smb_config_unlock(); + if (isenabled) { + if (adjoin(machine_passwd, + sizeof (machine_passwd)) == ADJOIN_SUCCESS) { + status = NT_STATUS_SUCCESS; + } else { + status = NT_STATUS_UNSUCCESSFUL; + } + } else { + /* + * Ensure that we don't have an old account in + * this domain. There's no need to check the + * return status. + */ + (void) sam_remove_trust_account(server, domain); + + if (mlsvc_user_getauth(server, plain_user, &auth) + != 0) { + status = NT_STATUS_INVALID_PARAMETER; + return (status); + } + + status = sam_create_trust_account(server, domain, + &auth); + if (status == NT_STATUS_SUCCESS) { + (void) smb_gethostname(machine_passwd, + sizeof (machine_passwd), 0); + (void) utf8_strlwr(machine_passwd); + } + } + + if (status == NT_STATUS_SUCCESS) { + smb_config_wrlock(); + if (smb_config_set(SMB_CI_MACHINE_PASSWD, + machine_passwd) != 0) { + smb_config_unlock(); + return (NT_STATUS_UNSUCCESSFUL); + } + smb_config_unlock(); + + /* + * If we successfully create a trust account, we mark + * ourselves as a domain member in the environment so + * that we use the SAMLOGON version of the NETLOGON + * PDC location protocol. + */ + smb_set_domain_member(1); + + if (netr_open(server, domain, &netr_handle) == 0) { + status = netlogon_auth(server, &netr_handle, + NETR_FLG_INIT); + (void) netr_close(&netr_handle); + } else { + status = NT_STATUS_OPEN_FAILED; + } + } + } else { + status = NT_STATUS_LOGON_FAILURE; + } + + return (status); +} + +/*ARGSUSED*/ +void +nt_group_ht_lock(krwmode_t locktype) +{ +} + +void +nt_group_ht_unlock(void) +{ +} + +int +nt_group_num_groups(void) +{ + return (0); +} + +/*ARGSUSED*/ +uint32_t +nt_group_add(char *gname, char *comment) +{ + return (NT_STATUS_NOT_SUPPORTED); +} + +/*ARGSUSED*/ +uint32_t +nt_group_modify(char *gname, char *new_gname, char *comment) +{ + return (NT_STATUS_NOT_SUPPORTED); +} + +/*ARGSUSED*/ +uint32_t +nt_group_delete(char *gname) +{ + return (NT_STATUS_NOT_SUPPORTED); +} + +/*ARGSUSED*/ +nt_group_t * +nt_group_getinfo(char *gname, krwmode_t locktype) +{ + return (NULL); +} + +/*ARGSUSED*/ +void +nt_group_putinfo(nt_group_t *grp) +{ +} + +/*ARGSUSED*/ +int +nt_group_getpriv(nt_group_t *grp, uint32_t priv_id) +{ + return (SE_PRIVILEGE_DISABLED); +} + +/*ARGSUSED*/ +uint32_t +nt_group_setpriv(nt_group_t *grp, uint32_t priv_id, uint32_t new_attr) +{ + return (NT_STATUS_NOT_SUPPORTED); +} + +/*ARGSUSED*/ +int +nt_group_is_member(nt_group_t *grp, nt_sid_t *sid) +{ + return (0); +} + +/*ARGSUSED*/ +uint32_t +nt_group_add_member(nt_group_t *grp, nt_sid_t *msid, uint16_t sid_name_use, + char *account) +{ + return (NT_STATUS_NOT_SUPPORTED); +} + +/*ARGSUSED*/ +uint32_t +nt_group_del_member(nt_group_t *grp, void *key, int keytype) +{ + return (NT_STATUS_NOT_SUPPORTED); +} + +/*ARGSUSED*/ +int +nt_group_num_members(nt_group_t *grp) +{ + return (0); +} + +nt_group_iterator_t * +nt_group_open_iterator(void) +{ + return (NULL); +} + +/*ARGSUSED*/ +void +nt_group_close_iterator(nt_group_iterator_t *gi) +{ +} + +/*ARGSUSED*/ +nt_group_t * +nt_group_iterate(nt_group_iterator_t *gi) +{ + return (NULL); +} + +int +nt_group_cache_size(void) +{ + return (0); +} + +uint32_t +sam_init(void) +{ + return (NT_STATUS_SUCCESS); +} + +/*ARGSUSED*/ +uint32_t +nt_group_add_member_byname(char *gname, char *account) +{ + return (NT_STATUS_NOT_SUPPORTED); +} + +/*ARGSUSED*/ +uint32_t +nt_group_del_member_byname(nt_group_t *grp, char *member_name) +{ + return (NT_STATUS_NOT_SUPPORTED); +} + +/*ARGSUSED*/ +void +nt_group_add_groupprivs(nt_group_t *grp, smb_privset_t *priv) +{ +} + +/*ARGSUSED*/ +uint32_t +nt_groups_member_privs(nt_sid_t *sid, smb_privset_t *priv) +{ + return (NT_STATUS_SUCCESS); +} + +/*ARGSUSED*/ +int +nt_groups_member_ngroups(nt_sid_t *sid) +{ + return (0); +} + +/*ARGSUSED*/ +uint32_t +nt_groups_member_groups(nt_sid_t *sid, smb_id_t *grps, int ngrps) +{ + return (NT_STATUS_SUCCESS); +} + +/*ARGSUSED*/ +nt_group_t * +nt_groups_lookup_rid(uint32_t rid) +{ + return (NULL); +} + +/*ARGSUSED*/ +int +nt_groups_count(int cnt_opt) +{ + return (0); +} + +/*ARGSUSED*/ +int +nt_group_member_list(int offset, nt_group_t *grp, + ntgrp_member_list_t *rmembers) +{ + return (0); +} + +/*ARGSUSED*/ +void +nt_group_list(int offset, char *pattern, ntgrp_list_t *list) +{ +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_winreg.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_winreg.c new file mode 100644 index 0000000000..bd2d5ee26f --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_winreg.c @@ -0,0 +1,454 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Windows Registry RPC (WINREG) server-side interface. + * + * The WINREG RPC interface returns Win32 error codes. + * + * HKLM Hive Key Local Machine + * HKU Hive Key Users + */ + +#include <sys/utsname.h> +#include <strings.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/nterror.h> +#include <smbsrv/nmpipes.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/winreg.ndl> + +/* + * List of mlsvc handle (local handle management) keys. + */ +#define WINREG_HKLM "WinregOpenHKLM" +#define WINREG_HKU "WinregOpenUser" +#define WINREG_KEY "WinregOpenKey" + +/* + * List of supported registry keys (case-insensitive). + * "System\\CurrentControlSet\\Services\\Alerter\\Parameters" + */ +static char *winreg_keys[] = { + "System\\CurrentControlSet\\Control\\ProductOptions", + "System\\CurrentControlSet\\Services\\Eventlog\\System" +}; + +static char *winreg_lookup_value(const char *); + +static int winreg_s_OpenHKLM(void *, struct mlrpc_xaction *); +static int winreg_s_OpenHKUsers(void *, struct mlrpc_xaction *); +static int winreg_s_Close(void *, struct mlrpc_xaction *); +static int winreg_s_CreateKey(void *, struct mlrpc_xaction *); +static int winreg_s_DeleteKey(void *, struct mlrpc_xaction *); +static int winreg_s_DeleteValue(void *, struct mlrpc_xaction *); +static int winreg_s_OpenKey(void *, struct mlrpc_xaction *); +static int winreg_s_QueryKey(void *, struct mlrpc_xaction *); +static int winreg_s_QueryValue(void *, struct mlrpc_xaction *); +static int winreg_s_CreateValue(void *, struct mlrpc_xaction *); +static int winreg_s_Shutdown(void *, struct mlrpc_xaction *); +static int winreg_s_GetVersion(void *, struct mlrpc_xaction *); + +static mlrpc_stub_table_t winreg_stub_table[] = { + { winreg_s_OpenHKLM, WINREG_OPNUM_OpenHKLM }, + { winreg_s_OpenHKUsers, WINREG_OPNUM_OpenHKUsers }, + { winreg_s_Close, WINREG_OPNUM_Close }, + { winreg_s_CreateKey, WINREG_OPNUM_CreateKey }, + { winreg_s_DeleteKey, WINREG_OPNUM_DeleteKey }, + { winreg_s_DeleteValue, WINREG_OPNUM_DeleteValue }, + { winreg_s_OpenKey, WINREG_OPNUM_OpenKey }, + { winreg_s_QueryKey, WINREG_OPNUM_QueryKey }, + { winreg_s_QueryValue, WINREG_OPNUM_QueryValue }, + { winreg_s_CreateValue, WINREG_OPNUM_CreateValue }, + { winreg_s_Shutdown, WINREG_OPNUM_Shutdown }, + { winreg_s_GetVersion, WINREG_OPNUM_GetVersion }, + {0} +}; + +static mlrpc_service_t winreg_service = { + "Winreg", /* name */ + "Windows Registry", /* desc */ + "\\winreg", /* endpoint */ + PIPE_WINREG, /* sec_addr_port */ + "338cd001-2244-31f1-aaaa900038001003", 1, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(winreg_interface), /* interface ti */ + winreg_stub_table /* stub_table */ +}; + +static char winreg_sysname[SYS_NMLN]; + +/* + * winreg_initialize + * + * This function registers the WINREG RPC interface with the RPC runtime + * library. It must be called in order to use either the client side + * or the server side functions. + */ +void +winreg_initialize(void) +{ + struct utsname name; + char *sysname; + + if (uname(&name) < 0) + sysname = "Solaris"; + else + sysname = name.sysname; + + (void) strlcpy(winreg_sysname, sysname, SYS_NMLN); + (void) mlrpc_register_service(&winreg_service); +} + +/* + * winreg_s_OpenHKLM + * + * This is a request to open the HKLM and get a handle. The client + * should treat the handle as an opaque object. + * + * Status: + * ERROR_SUCCESS Valid handle returned. + * ERROR_ACCESS_DENIED Unable to allocate a handle. + */ +/*ARGSUSED*/ +static int +winreg_s_OpenHKLM(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_OpenHKLM *param = arg; + ms_handle_t *handle; + + handle = mlsvc_get_handle(MLSVC_IFSPEC_WINREG, WINREG_HKLM, 0); + if (handle == NULL) { + bzero(¶m->handle, sizeof (msreg_handle_t)); + param->status = ERROR_ACCESS_DENIED; + } else { + bcopy(handle, ¶m->handle, sizeof (msreg_handle_t)); + param->status = ERROR_SUCCESS; + } + + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_OpenHKUsers + * + * This is a request to get a HKUsers handle. I'm not sure we are + * ready to fully support this interface yet, mostly due to the need + * to support subsequent requests, but we may support enough now. It + * seems okay with regedt32. + */ +/*ARGSUSED*/ +static int +winreg_s_OpenHKUsers(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_OpenHKUsers *param = arg; + ms_handle_t *handle; + + handle = mlsvc_get_handle(MLSVC_IFSPEC_WINREG, WINREG_HKU, 0); + if (handle == NULL) { + bzero(¶m->handle, sizeof (msreg_handle_t)); + param->status = ERROR_ACCESS_DENIED; + } else { + bcopy(handle, ¶m->handle, sizeof (msreg_handle_t)); + param->status = ERROR_SUCCESS; + } + + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_Close + * + * This is a request to close the WINREG interface specified by the + * handle. We don't track handles (yet), so just zero out the handle + * and return MLRPC_DRC_OK. Setting the handle to zero appears to be + * standard behaviour. + */ +/*ARGSUSED*/ +static int +winreg_s_Close(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_Close *param = arg; + + (void) mlsvc_put_handle((ms_handle_t *)¶m->handle); + bzero(¶m->result_handle, sizeof (msreg_handle_t)); + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_CreateKey + */ +/*ARGSUSED*/ +static int +winreg_s_CreateKey(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_CreateKey *param = arg; + + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_DeleteKey + */ +/*ARGSUSED*/ +static int +winreg_s_DeleteKey(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_DeleteKey *param = arg; + + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_DeleteValue + */ +/*ARGSUSED*/ +static int +winreg_s_DeleteValue(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_DeleteValue *param = arg; + + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_OpenKey + * + * This is a request to open a windows registry key. The list + * of supported keys is listed in the winreg_keys table. If we + * recognize the key, we return a handle. + * + * Returns: + * ERROR_SUCCESS Valid handle returned. + * ERROR_FILE_NOT_FOUND No key or unable to allocate a handle. + */ +/*ARGSUSED*/ +static int +winreg_s_OpenKey(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_OpenKey *param = arg; + ms_handle_t *handle; + char *key = (char *)param->name.str; + int i; + + for (i = 0; i < sizeof (winreg_keys)/sizeof (winreg_keys[0]); ++i) { + if (strcasecmp(key, winreg_keys[i]) == 0) { + handle = mlsvc_get_handle(MLSVC_IFSPEC_WINREG, + WINREG_KEY, 0); + + if (handle == NULL) + break; + + bcopy(handle, ¶m->result_handle, + sizeof (msreg_handle_t)); + + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); + } + } + + bzero(¶m->result_handle, sizeof (msreg_handle_t)); + param->status = ERROR_FILE_NOT_FOUND; + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_QueryKey + */ +/*ARGSUSED*/ +static int +winreg_s_QueryKey(void *arg, struct mlrpc_xaction *mxa) +{ + static char nullstr[2] = { 0, 0 }; + struct msreg_QueryKey *param = arg; + + bzero(param, sizeof (struct msreg_QueryKey)); + + param->name.length = 2; + param->name.allosize = 0; + param->name.str = (unsigned char *)nullstr; + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_QueryValue + * + * This is a request to get the value associated with a specified name. + * + * Returns: + * ERROR_SUCCESS Value returned. + * ERROR_FILE_NOT_FOUND PrimaryModule is not supported. + * ERROR_CANTREAD No such name or memory problem. + */ +static int +winreg_s_QueryValue(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_QueryValue *param = arg; + struct msreg_value *pv; + char *name; + char *value; + DWORD slen; + DWORD msize; + + name = (char *)param->value_name.str; + + if (strcasecmp(name, "PrimaryModule") == 0) { + param->status = ERROR_FILE_NOT_FOUND; + return (MLRPC_DRC_OK); + } + + if ((value = winreg_lookup_value(name)) == NULL) { + param->status = ERROR_CANTREAD; + return (MLRPC_DRC_OK); + } + + slen = mts_wcequiv_strlen(value) + sizeof (mts_wchar_t); + msize = sizeof (struct msreg_value) + slen; + + param->value = (struct msreg_value *)MLRPC_HEAP_MALLOC(mxa, msize); + param->type = MLRPC_HEAP_NEW(mxa, DWORD); + param->value_size = MLRPC_HEAP_NEW(mxa, DWORD); + param->value_size_total = MLRPC_HEAP_NEW(mxa, DWORD); + + if (param->value == NULL || param->type == NULL || + param->value_size == NULL || param->value_size_total == NULL) { + param->status = ERROR_CANTREAD; + return (MLRPC_DRC_OK); + } + + bzero(param->value, msize); + pv = param->value; + pv->vc_first_is = 0; + pv->vc_length_is = slen; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + (void) mts_mbstowcs((mts_wchar_t *)pv->value, value, slen); + + *param->type = 1; + *param->value_size = slen; + *param->value_size_total = slen; + + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * Lookup a name in the registry and return the associated value. + * Our registry is a case-insensitive, name-value pair table. + * + * Windows ProductType: WinNT, ServerNT, LanmanNT. + * Windows NT4.0 workstation: WinNT + * Windows NT4.0 server: ServerNT + * + * If LanmanNT is used here, Windows 2000 sends LsarQueryInfoPolicy + * with info level 6, which we don't support. If we use ServerNT + * (as reported by NT4.0 Server) Windows 2000 send requests for + * levels 3 and 5, which are support. + * + * On success, returns a pointer to the value. Otherwise returns + * a null pointer. + */ +static char * +winreg_lookup_value(const char *name) +{ + static struct registry { + char *name; + char *value; + } registry[] = { + { "ProductType", "ServerNT" }, + { "Sources", NULL } /* product name */ + }; + + int i; + + for (i = 0; i < sizeof (registry)/sizeof (registry[0]); ++i) { + if (strcasecmp(registry[i].name, name) == 0) { + if (registry[i].value == NULL) + return (winreg_sysname); + else + return (registry[i].value); + } + } + + return (0); +} + +/* + * winreg_s_CreateValue + */ +/*ARGSUSED*/ +static int +winreg_s_CreateValue(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_CreateValue *param = arg; + + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_Shutdown + * + * Attempt to shutdown or reboot the system: access denied. + */ +/*ARGSUSED*/ +static int +winreg_s_Shutdown(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_Shutdown *param = arg; + + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * winreg_s_GetVersion + * + * Return the windows registry version. The current version is 5. + * This call is usually made prior to enumerating or querying registry + * keys or values. + */ +/*ARGSUSED*/ +static int +winreg_s_GetVersion(void *arg, struct mlrpc_xaction *mxa) +{ + struct msreg_GetVersion *param = arg; + + param->version = 5; + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_wkssvc.c b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_wkssvc.c new file mode 100644 index 0000000000..b7453b171e --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/mlsvc_wkssvc.c @@ -0,0 +1,142 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <netdb.h> +#include <sys/types.h> +#include <string.h> +#include <strings.h> +#include <smbsrv/libsmb.h> +#include <smbsrv/libmlsvc.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/nmpipes.h> +#include <smbsrv/nterror.h> +#include <smbsrv/lmerr.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/srvsvc.ndl> + +static int wkssvc_s_NetWkstaGetInfo(void *, struct mlrpc_xaction *); + +static mlrpc_stub_table_t wkssvc_stub_table[] = { + { wkssvc_s_NetWkstaGetInfo, WKSSVC_OPNUM_NetWkstaGetInfo }, + {0} +}; + +static mlrpc_service_t wkssvc_service = { + "Workstation", /* name (WKSSVC or WKSTA) */ + "Workstation services", /* desc */ + "\\wkssvc", /* endpoint */ + PIPE_NTSVCS, /* sec_addr_port */ + "6bffd098-a112-3610-983346c3f87e345a", 1, /* abstract */ + "8a885d04-1ceb-11c9-9fe808002b104860", 2, /* transfer */ + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + &TYPEINFO(wkssvc_interface), /* interface ti */ + wkssvc_stub_table /* stub_table */ +}; + +void +wkssvc_initialize(void) +{ + (void) mlrpc_register_service(&wkssvc_service); +} + +/* + * WKSSVC NetWkstaGetInfo ( + * IN LPTSTR servername, + * IN DWORD level, + * OUT union switch(level) { + * case 100: _WKSTA_INFO_100 * p100; + * case 101: _WKSTA_INFO_101 * p101; + * case 102: _WKSTA_INFO_102 * p102; + * } bufptr, + * OUT DWORD status + * ) + */ +static int +wkssvc_s_NetWkstaGetInfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct mslm_NetWkstaGetInfo *param = arg; + mslm_NetWkstaGetInfo_rb *rb; + char hostname[MAXHOSTNAMELEN]; + char *resource_domain; + char *p; + DWORD status; + int rc; + + rc = smb_getnetbiosname(hostname, MAXHOSTNAMELEN); + rb = MLRPC_HEAP_NEW(mxa, mslm_NetWkstaGetInfo_rb); + + if ((rc != 0) || (rb == NULL)) { + bzero(param, sizeof (struct mslm_NetWkstaGetInfo)); + param->status = ERROR_NOT_ENOUGH_MEMORY; + return (MLRPC_DRC_OK); + } + + param->result.level = param->level; + param->result.bufptr.nullptr = (void *) rb; + + switch (param->level) { + case 100: + rb->buf100.wki100_platform_id = SV_PLATFORM_ID_NT; + rb->buf100.wki100_ver_major = 4; + rb->buf100.wki100_ver_minor = 0; + + if ((p = MLRPC_HEAP_STRSAVE(mxa, hostname)) == NULL) { + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + rb->buf100.wki100_computername = (unsigned char *)p; + + smb_config_rdlock(); + resource_domain = smb_config_getstr(SMB_CI_DOMAIN_NAME); + + if ((p = MLRPC_HEAP_STRSAVE(mxa, resource_domain)) == NULL) { + smb_config_unlock(); + status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + + smb_config_unlock(); + rb->buf100.wki100_langroup = (unsigned char *)p; + status = ERROR_SUCCESS; + break; + + default: + param->result.bufptr.nullptr = 0; + status = ERROR_INVALID_LEVEL; + break; + } + + if (status != ERROR_SUCCESS) { + bzero(param, sizeof (struct mslm_NetWkstaGetInfo)); + param->status = status; + } + + return (MLRPC_DRC_OK); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/netdfs.c b/usr/src/lib/smbsrv/libmlsvc/common/netdfs.c new file mode 100644 index 0000000000..aad7e1bbd0 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/netdfs.c @@ -0,0 +1,519 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Net DFS server side RPC service. + */ + +#include <sys/types.h> +#include <strings.h> +#include <string.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/lmerr.h> +#include <smbsrv/lmdfs.h> +#include <smbsrv/nmpipes.h> +#include <smbsrv/nterror.h> +#include <smbsrv/mlrpc.h> +#include <smbsrv/ndl/netdfs.ndl> + +typedef struct { + char *server; + char *share; + char *path; + char *buf; +} netdfs_unc_t; + +static int netdfs_unc_parse(struct mlrpc_xaction *, const char *, + netdfs_unc_t *); + +static int netdfs_s_getver(void *, struct mlrpc_xaction *); +static int netdfs_s_add(void *, struct mlrpc_xaction *); +static int netdfs_s_remove(void *, struct mlrpc_xaction *); +static int netdfs_s_setinfo(void *, struct mlrpc_xaction *); +static int netdfs_s_getinfo(void *, struct mlrpc_xaction *); +static int netdfs_s_enum(void *, struct mlrpc_xaction *); +static int netdfs_s_move(void *, struct mlrpc_xaction *); +static int netdfs_s_rename(void *, struct mlrpc_xaction *); +static int netdfs_s_addstdroot(void *, struct mlrpc_xaction *); +static int netdfs_s_remstdroot(void *, struct mlrpc_xaction *); +static int netdfs_s_enumex(void *, struct mlrpc_xaction *); + +static mlrpc_stub_table_t netdfs_stub_table[] = { + { netdfs_s_getver, NETDFS_OPNUM_GETVER }, + { netdfs_s_add, NETDFS_OPNUM_ADD }, + { netdfs_s_remove, NETDFS_OPNUM_REMOVE }, + { netdfs_s_setinfo, NETDFS_OPNUM_SETINFO }, + { netdfs_s_getinfo, NETDFS_OPNUM_GETINFO }, + { netdfs_s_enum, NETDFS_OPNUM_ENUM }, + { netdfs_s_rename, NETDFS_OPNUM_RENAME }, + { netdfs_s_move, NETDFS_OPNUM_MOVE }, + { netdfs_s_addstdroot, NETDFS_OPNUM_ADDSTDROOT }, + { netdfs_s_remstdroot, NETDFS_OPNUM_REMSTDROOT }, + { netdfs_s_enumex, NETDFS_OPNUM_ENUMEX }, + {0} +}; + +static mlrpc_service_t netdfs_service = { + "NETDFS", /* name */ + "DFS", /* desc */ + "\\dfs", /* endpoint */ + PIPE_NTSVCS, /* sec_addr_port */ + NETDFS_ABSTRACT_UUID, NETDFS_ABSTRACT_VERS, + NETDFS_TRANSFER_UUID, NETDFS_TRANSFER_VERS, + + 0, /* no bind_instance_size */ + 0, /* no bind_req() */ + 0, /* no unbind_and_close() */ + 0, /* use generic_call_stub() */ + + &TYPEINFO(netdfs_interface), /* interface ti */ + netdfs_stub_table /* stub_table */ +}; + +/* + * Register the NETDFS RPC interface with the RPC runtime library. + * The service must be registered in order to use either the client + * side or the server side functions. + */ +void +netdfs_initialize(void) +{ + (void) mlrpc_register_service(&netdfs_service); +} + +/* + * Return the version. + * + * We have to indicate that we emulate a Windows 2003 Server or the + * client will not use the EnumEx RPC and this would limit support + * to a single DFS root. + */ +/*ARGSUSED*/ +static int +netdfs_s_getver(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_getver *param = arg; + + param->version = DFS_MANAGER_VERSION_W2K3; + return (MLRPC_DRC_OK); +} + +/* + * Add a new volume or additional storage for an existing volume at + * dfs_path. + */ +static int +netdfs_s_add(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_add *param = arg; + netdfs_unc_t unc; + DWORD status = ERROR_SUCCESS; + + if (param->dfs_path == NULL || param->server == NULL || + param->share == NULL) { + bzero(param, sizeof (struct netdfs_add)); + param->status = ERROR_INVALID_PARAMETER; + return (MLRPC_DRC_OK); + } + + if (netdfs_unc_parse(mxa, (char *)param->dfs_path, &unc) != 0) { + status = ERROR_INVALID_PARAMETER; + } else { + if (unc.path == NULL) + status = ERROR_BAD_PATHNAME; + + if (unc.share == NULL) + status = ERROR_INVALID_SHARENAME; + } + + if (param->status != ERROR_SUCCESS) { + bzero(param, sizeof (struct netdfs_add)); + param->status = status; + return (MLRPC_DRC_OK); + } + + bzero(param, sizeof (struct netdfs_add)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * netdfs_s_remove + * + * Remove a volume or additional storage for volume from the DFS at + * dfs_path. When applied to the last storage in a volume, removes + * the volume from the DFS. + */ +static int +netdfs_s_remove(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_remove *param = arg; + netdfs_unc_t unc; + DWORD status = ERROR_SUCCESS; + + if (param->dfs_path == NULL || param->server == NULL || + param->share == NULL) { + bzero(param, sizeof (struct netdfs_remove)); + param->status = ERROR_INVALID_PARAMETER; + return (MLRPC_DRC_OK); + } + + if (netdfs_unc_parse(mxa, (char *)param->dfs_path, &unc) != 0) { + status = ERROR_INVALID_PARAMETER; + } else { + if (unc.path == NULL) + status = ERROR_BAD_PATHNAME; + + if (unc.share == NULL) + status = ERROR_INVALID_SHARENAME; + } + + if (param->status != ERROR_SUCCESS) { + bzero(param, sizeof (struct netdfs_remove)); + param->status = status; + return (MLRPC_DRC_OK); + } + + bzero(param, sizeof (struct netdfs_remove)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * Set information about the volume or storage. If the server and share + * are specified, the information set is specific to that server and + * share. Otherwise the information is specific to the volume as a whole. + * + * Valid levels are 100-102. + */ +/*ARGSUSED*/ +static int +netdfs_s_setinfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_setinfo *param = arg; + netdfs_unc_t unc; + DWORD status = ERROR_SUCCESS; + + if (param->dfs_path == NULL) { + bzero(param, sizeof (struct netdfs_setinfo)); + param->status = ERROR_INVALID_PARAMETER; + return (MLRPC_DRC_OK); + } + + if (netdfs_unc_parse(mxa, (char *)param->dfs_path, &unc) != 0) { + status = ERROR_INVALID_PARAMETER; + } else { + if (unc.share == NULL) + status = ERROR_INVALID_SHARENAME; + } + + if (param->status != ERROR_SUCCESS) { + bzero(param, sizeof (struct netdfs_setinfo)); + param->status = status; + return (MLRPC_DRC_OK); + } + + switch (param->info.level) { + case 100: + case 101: + case 102: + break; + + default: + bzero(param, sizeof (struct netdfs_setinfo)); + param->status = ERROR_INVALID_LEVEL; + return (MLRPC_DRC_OK); + } + + bzero(param, sizeof (struct netdfs_setinfo)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * Get information about the volume or storage. If the server and share + * are specified, the information returned is specific to that server + * and share. Otherwise the information is specific to the volume as a + * whole. + * + * Valid levels are 1-4, 100-104. + */ +/*ARGSUSED*/ +static int +netdfs_s_getinfo(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_getinfo *param = arg; + netdfs_unc_t unc; + DWORD status = ERROR_SUCCESS; + + if (param->dfs_path == NULL) { + bzero(param, sizeof (struct netdfs_getinfo)); + param->status = ERROR_INVALID_PARAMETER; + return (MLRPC_DRC_OK); + } + + if (netdfs_unc_parse(mxa, (char *)param->dfs_path, &unc) != 0) { + status = ERROR_INVALID_PARAMETER; + } else { + if (unc.share == NULL) + status = ERROR_INVALID_SHARENAME; + } + + if (param->status != ERROR_SUCCESS) { + bzero(param, sizeof (struct netdfs_getinfo)); + param->status = status; + return (MLRPC_DRC_OK); + } + + switch (param->level) { + case 1: + case 2: + case 3: + case 4: + case 100: + case 101: + case 102: + case 103: + case 104: + break; + + default: + bzero(param, sizeof (struct netdfs_getinfo)); + param->status = ERROR_INVALID_LEVEL; + return (MLRPC_DRC_OK); + } + + bzero(param, sizeof (struct netdfs_getinfo)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * Get information about all of the volumes in the DFS. dfs_name is + * the "server" part of the UNC name used to refer to this particular + * DFS. + * + * Valid levels are 1-3. + */ +/*ARGSUSED*/ +static int +netdfs_s_enum(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_enum *param = arg; + + switch (param->level) { + case 1: + case 2: + case 3: + break; + + default: + (void) bzero(param, sizeof (struct netdfs_enum)); + param->status = ERROR_INVALID_LEVEL; + return (MLRPC_DRC_OK); + } + + (void) bzero(param, sizeof (struct netdfs_enum)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * Move a DFS volume and all subordinate volumes from one place in the + * DFS to another place in the DFS. + */ +/*ARGSUSED*/ +static int +netdfs_s_move(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_move *param = arg; + + if (param->dfs_path == NULL || param->new_path == NULL) { + bzero(param, sizeof (struct netdfs_move)); + param->status = ERROR_INVALID_PARAMETER; + return (MLRPC_DRC_OK); + } + + bzero(param, sizeof (struct netdfs_move)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * Rename the current path in a DFS to a new path in the same DFS. + */ +/*ARGSUSED*/ +static int +netdfs_s_rename(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_rename *param = arg; + + if (param->dfs_path == NULL || param->new_path == NULL) { + bzero(param, sizeof (struct netdfs_rename)); + param->status = ERROR_INVALID_PARAMETER; + return (MLRPC_DRC_OK); + } + + bzero(param, sizeof (struct netdfs_rename)); + param->status = ERROR_ACCESS_DENIED; + return (MLRPC_DRC_OK); +} + +/* + * Add a DFS root share. + */ +/*ARGSUSED*/ +static int +netdfs_s_addstdroot(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_addstdroot *param = arg; + + bzero(param, sizeof (struct netdfs_addstdroot)); + param->status = ERROR_INVALID_PARAMETER; + return (MLRPC_DRC_OK); +} + +/* + * Remove a DFS root share. + */ +/*ARGSUSED*/ +static int +netdfs_s_remstdroot(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_remstdroot *param = arg; + + bzero(param, sizeof (struct netdfs_remstdroot)); + param->status = ERROR_INVALID_PARAMETER; + return (MLRPC_DRC_OK); +} + +/* + * Get information about all of the volumes in the DFS. dfs_path is + * the "server" part of the UNC name used to refer to this particular + * DFS. + * + * Valid levels are 1-3, 300. + */ +static int +netdfs_s_enumex(void *arg, struct mlrpc_xaction *mxa) +{ + struct netdfs_enumex *param = arg; + netdfs_unc_t unc; + DWORD status = ERROR_SUCCESS; + + if (param->dfs_path == NULL) { + bzero(param, sizeof (struct netdfs_enumex)); + param->status = ERROR_INVALID_PARAMETER; + return (MLRPC_DRC_OK); + } + + if (param->resume_handle == NULL) + param->resume_handle = MLRPC_HEAP_NEW(mxa, DWORD); + + if (param->resume_handle) + *(param->resume_handle) = 0; + + if (netdfs_unc_parse(mxa, (char *)param->dfs_path, &unc) != 0) { + status = ERROR_INVALID_PARAMETER; + } else { + if (unc.path == NULL) + status = ERROR_BAD_PATHNAME; + + if (unc.share == NULL) + status = ERROR_INVALID_SHARENAME; + } + + if (param->status != ERROR_SUCCESS) { + bzero(param, sizeof (struct netdfs_enumex)); + param->status = status; + return (MLRPC_DRC_OK); + } + + param->info = MLRPC_HEAP_NEW(mxa, struct netdfs_enum_info); + if (param->info == NULL) { + bzero(param, sizeof (struct netdfs_enumex)); + param->status = ERROR_NOT_ENOUGH_MEMORY; + return (MLRPC_DRC_OK); + } + + bzero(param->info, sizeof (struct netdfs_enumex)); + param->status = ERROR_SUCCESS; + return (MLRPC_DRC_OK); +} + +/* + * Parse a UNC path (\\server\share\path) into components. + * Path separators are converted to forward slashes. + * + * Returns 0 on success, otherwise -1 to indicate an error. + */ +static int +netdfs_unc_parse(struct mlrpc_xaction *mxa, const char *path, netdfs_unc_t *unc) +{ + char *p; + + if (path == NULL || unc == NULL) + return (-1); + + if ((unc->buf = MLRPC_HEAP_STRSAVE(mxa, (char *)path)) == NULL) + return (-1); + + if ((p = strchr(unc->buf, '\n')) != NULL) + *p = '\0'; + + (void) strsubst(unc->buf, '\\', '/'); + (void) strcanon(unc->buf, "/"); + + unc->server = unc->buf; + unc->server += strspn(unc->buf, "/"); + + if (unc->server) { + unc->share = strchr(unc->server, '/'); + if ((p = unc->share) != NULL) { + unc->share += strspn(unc->share, "/"); + *p = '\0'; + } + } + + if (unc->share) { + unc->path = strchr(unc->share, '/'); + if ((p = unc->path) != NULL) { + unc->path += strspn(unc->path, "/"); + *p = '\0'; + } + } + + if (unc->path) { + if ((p = strchr(unc->path, '\0')) != NULL) { + if (*(--p) == '/') + *p = '\0'; + } + } + + return (0); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/netr_auth.c b/usr/src/lib/smbsrv/libmlsvc/common/netr_auth.c new file mode 100644 index 0000000000..2eb3e78bb7 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/netr_auth.c @@ -0,0 +1,502 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * NETR challenge/response client functions. + * + * NT_STATUS_INVALID_PARAMETER + * NT_STATUS_NO_TRUST_SAM_ACCOUNT + * NT_STATUS_ACCESS_DENIED + */ + +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <ctype.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/ndl/netlogon.ndl> +#include <smbsrv/ntstatus.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/mlsvc.h> +#include <smbsrv/netrauth.h> + +int netr_setup_authenticator(netr_info_t *, struct netr_authenticator *, + struct netr_authenticator *); +DWORD netr_validate_chain(netr_info_t *, struct netr_authenticator *); + +static int netr_server_req_challenge(mlsvc_handle_t *, netr_info_t *); +static int netr_server_authenticate2(mlsvc_handle_t *, netr_info_t *); +static int netr_gen_password(BYTE *, BYTE *, BYTE *); + +/* + * Shared with netr_logon.c + */ +netr_info_t netr_global_info; + +/* + * netlogon_auth + * + * This is the core of the NETLOGON authentication protocol. + * Do the challenge response authentication. + * + * Prior to calling this function, an anonymous session to the NETLOGON + * pipe on a domain controller(server) should have already been opened. + */ +DWORD +netlogon_auth(char *server, mlsvc_handle_t *netr_handle, DWORD flags) +{ + netr_info_t *netr_info; + int rc; + DWORD random_challenge[2]; + + netr_info = &netr_global_info; + bzero(netr_info, sizeof (netr_info_t)); + + netr_info->flags |= flags; + + rc = smb_getnetbiosname(netr_info->hostname, MLSVC_DOMAIN_NAME_MAX); + if (rc != 0) + return (NT_STATUS_UNSUCCESSFUL); + + (void) snprintf(netr_info->server, sizeof (netr_info->server), + "\\\\%s", server); + + random_challenge[0] = random(); + random_challenge[1] = random(); + + (void) memcpy(&netr_info->client_challenge, random_challenge, + sizeof (struct netr_credential)); + + if ((rc = netr_server_req_challenge(netr_handle, netr_info)) == 0) { + rc = netr_server_authenticate2(netr_handle, netr_info); + if (rc == 0) + netr_info->flags |= NETR_FLG_VALID; + } + + return ((rc) ? NT_STATUS_UNSUCCESSFUL : NT_STATUS_SUCCESS); +} + +/* + * netr_open + * + * Open an anonymous session to the NETLOGON pipe on a domain + * controller and bind to the NETR RPC interface. We store the + * remote server's native OS type - we may need it due to + * differences between versions of Windows. + */ +int +netr_open(char *server, char *domain, mlsvc_handle_t *netr_handle) +{ + int fid; + int remote_os = 0; + int remote_lm = 0; + int server_pdc; + char *username; + + if (mlsvc_anonymous_logon(server, domain, &username) != 0) + return (-1); + + fid = mlsvc_open_pipe(server, domain, username, "\\NETLOGON"); + if (fid < 0) + return (-1); + + if (mlsvc_rpc_bind(netr_handle, fid, "NETR") < 0) { + (void) mlsvc_close_pipe(fid); + return (-1); + } + + (void) mlsvc_session_native_values(fid, &remote_os, &remote_lm, + &server_pdc); + netr_handle->context->server_os = remote_os; + netr_handle->context->server_pdc = server_pdc; + return (0); +} + +/* + * netr_close + * + * Close a NETLOGON pipe and free the RPC context. + */ +int +netr_close(mlsvc_handle_t *netr_handle) +{ + (void) mlsvc_close_pipe(netr_handle->context->fid); + free(netr_handle->context); + return (0); +} + +/* + * netr_server_req_challenge + */ +static int +netr_server_req_challenge(mlsvc_handle_t *netr_handle, netr_info_t *netr_info) +{ + struct netr_ServerReqChallenge arg; + mlrpc_heapref_t heap; + int opnum; + int rc; + + bzero(&arg, sizeof (struct netr_ServerReqChallenge)); + opnum = NETR_OPNUM_ServerReqChallenge; + + arg.servername = (unsigned char *)netr_info->server; + arg.hostname = (unsigned char *)netr_info->hostname; + + (void) memcpy(&arg.client_challenge, &netr_info->client_challenge, + sizeof (struct netr_credential)); + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(netr_handle->context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + rc = -1; + } else { + (void) memcpy(&netr_info->server_challenge, + &arg.server_challenge, + sizeof (struct netr_credential)); + } + } + + mlsvc_rpc_free(netr_handle->context, &heap); + return (rc); +} + +/* + * netr_server_authenticate2 + */ +static int +netr_server_authenticate2(mlsvc_handle_t *netr_handle, netr_info_t *netr_info) +{ + struct netr_ServerAuthenticate2 arg; + mlrpc_heapref_t heap; + int opnum; + int rc; + char account_name[MLSVC_DOMAIN_NAME_MAX * 2]; + + bzero(&arg, sizeof (struct netr_ServerAuthenticate2)); + opnum = NETR_OPNUM_ServerAuthenticate2; + + (void) snprintf(account_name, sizeof (account_name), "%s$", + netr_info->hostname); + + arg.servername = (unsigned char *)netr_info->server; + arg.account_name = (unsigned char *)account_name; + arg.account_type = NETR_WKSTA_TRUST_ACCOUNT_TYPE; + arg.hostname = (unsigned char *)netr_info->hostname; + arg.negotiate_flags = NETR_NEGOTIATE_FLAGS; + + smb_tracef("server=[%s] account_name=[%s] hostname=[%s]\n", + netr_info->server, account_name, netr_info->hostname); + + if (netr_gen_session_key(netr_info) != SMBAUTH_SUCCESS) + return (-1); + + if (netr_gen_credentials(netr_info->session_key, + &netr_info->client_challenge, + 0, + &netr_info->client_credential) != SMBAUTH_SUCCESS) { + return (-1); + } + + if (netr_gen_credentials(netr_info->session_key, + &netr_info->server_challenge, + 0, + &netr_info->server_credential) != SMBAUTH_SUCCESS) { + return (-1); + } + + (void) memcpy(&arg.client_credential, &netr_info->client_credential, + sizeof (struct netr_credential)); + + (void) mlsvc_rpc_init(&heap); + + rc = mlsvc_rpc_call(netr_handle->context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + rc = -1; + } else { + rc = memcmp(&netr_info->server_credential, + &arg.server_credential, + sizeof (struct netr_credential)); + } + } + + mlsvc_rpc_free(netr_handle->context, &heap); + return (rc); +} + +/* + * netr_gen_session_key + * + * Generate a session key from the client and server challenges. The + * algorithm is a two stage hash. For the first hash, the input is + * the combination of the client and server challenges, the key is + * the first 8 bytes of the password. The initial password is formed + * using the NT password hash on the local hostname in lower case. + * The result is stored in a temporary buffer. + * + * input: challenge + * key: passwd lower 8 bytes + * output: intermediate result + * + * For the second hash, the input is the result of the first hash and + * the key is the last 8 bytes of the password. + * + * input: result of first hash + * key: passwd upper 8 bytes + * output: session_key + * + * The final output should be the session key. + * + * FYI: smb_auth_DES(output, key, input) + * + * If any difficulties occur using the cryptographic framework, the + * function returns SMBAUTH_FAILURE. Otherwise SMBAUTH_SUCCESS is + * returned. + */ +int +netr_gen_session_key(netr_info_t *netr_info) +{ + unsigned char md4hash[32]; + unsigned char buffer[8]; + DWORD data[2]; + DWORD *client_challenge; + DWORD *server_challenge; + int rc; + char *machine_passwd; + DWORD new_data[2]; + + client_challenge = (DWORD *)(uintptr_t)&netr_info->client_challenge; + server_challenge = (DWORD *)(uintptr_t)&netr_info->server_challenge; + bzero(md4hash, 32); + + /* + * We should check (netr_info->flags & NETR_FLG_INIT) and use + * the appropriate password but it isn't working yet. So we + * always use the default one for now. + */ + smb_config_rdlock(); + machine_passwd = smb_config_getstr(SMB_CI_MACHINE_PASSWD); + + if (!machine_passwd || *machine_passwd == 0) { + smb_config_unlock(); + return (-1); + } + + bzero(netr_info->password, sizeof (netr_info->password)); + (void) strlcpy((char *)netr_info->password, (char *)machine_passwd, + sizeof (netr_info->password)); + + rc = smb_auth_ntlm_hash((char *)machine_passwd, md4hash); + smb_config_unlock(); + + if (rc != SMBAUTH_SUCCESS) + return (SMBAUTH_FAILURE); + + data[0] = LE_IN32(&client_challenge[0]) + LE_IN32(&server_challenge[0]); + data[1] = LE_IN32(&client_challenge[1]) + LE_IN32(&server_challenge[1]); + LE_OUT32(&new_data[0], data[0]); + LE_OUT32(&new_data[1], data[1]); + + rc = smb_auth_DES(buffer, 8, md4hash, 8, (unsigned char *)new_data, 8); + if (rc != SMBAUTH_SUCCESS) + return (rc); + + rc = smb_auth_DES(netr_info->session_key, 8, &md4hash[9], 8, buffer, 8); + return (rc); +} + +/* + * netr_gen_credentials + * + * Generate a set of credentials from a challenge and a session key. + * The algorithm is a two stage hash. For the first hash, the + * timestamp is added to the challenge and the result is stored in a + * temporary buffer: + * + * input: challenge (including timestamp) + * key: session_key + * output: intermediate result + * + * For the second hash, the input is the result of the first hash and + * a strange partial key is used: + * + * input: result of first hash + * key: funny partial key + * output: credentiails + * + * The final output should be an encrypted set of credentials. + * + * FYI: smb_auth_DES(output, key, input) + * + * If any difficulties occur using the cryptographic framework, the + * function returns SMBAUTH_FAILURE. Otherwise SMBAUTH_SUCCESS is + * returned. + */ +int +netr_gen_credentials(BYTE *session_key, netr_cred_t *challenge, + DWORD timestamp, netr_cred_t *out_cred) +{ + unsigned char buffer[8]; + unsigned char partial_key[8]; + DWORD data[2]; + DWORD *p; + int rc; + + p = (DWORD *)(uintptr_t)challenge; + data[0] = p[0] + LE_IN32(×tamp); + data[1] = p[1]; + + if (smb_auth_DES(buffer, 8, session_key, 8, + (unsigned char *)data, 8) != SMBAUTH_SUCCESS) + return (SMBAUTH_FAILURE); + + bzero(partial_key, 8); + partial_key[0] = session_key[7]; + + rc = smb_auth_DES((unsigned char *)out_cred, 8, partial_key, 8, + buffer, 8); + return (rc); +} + +/* + * netr_server_password_set + * + * Attempt to change the trust account password for this system. + * + * Note that this call may legitimately fail if the registry on the + * domain controller has been setup to deny attempts to change the + * trust account password. In this case we should just continue to + * use the original password. + * + * Possible status values: + * NT_STATUS_ACCESS_DENIED + */ +int +netr_server_password_set(mlsvc_handle_t *netr_handle, netr_info_t *netr_info) +{ + struct netr_PasswordSet arg; + mlrpc_heapref_t heap; + int opnum; + int rc; + BYTE new_password[NETR_OWF_PASSWORD_SZ]; + char account_name[MLSVC_DOMAIN_NAME_MAX * 2]; + + bzero(&arg, sizeof (struct netr_PasswordSet)); + opnum = NETR_OPNUM_ServerPasswordSet; + + (void) snprintf(account_name, sizeof (account_name), "%s$", + netr_info->hostname); + + arg.servername = (unsigned char *)netr_info->server; + arg.account_name = (unsigned char *)account_name; + arg.account_type = NETR_WKSTA_TRUST_ACCOUNT_TYPE; + arg.hostname = (unsigned char *)netr_info->hostname; + + /* + * Set up the client side authenticator. + */ + if (netr_setup_authenticator(netr_info, &arg.auth, 0) != + SMBAUTH_SUCCESS) { + return (-1); + } + + /* + * Generate a new password from the old password. + */ + if (netr_gen_password(netr_info->session_key, + netr_info->password, new_password) == SMBAUTH_FAILURE) { + return (-1); + } + + (void) memcpy(&arg.uas_new_password, &new_password, + NETR_OWF_PASSWORD_SZ); + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(netr_handle->context, opnum, &arg, &heap); + if ((rc != 0) || (arg.status != 0)) { + mlsvc_rpc_report_status(opnum, arg.status); + mlsvc_rpc_free(netr_handle->context, &heap); + return (-1); + } + + /* + * Check the returned credentials. The server returns the new + * client credential rather than the new server credentiali, + * as documented elsewhere. + * + * Generate the new seed for the credential chain. Increment + * the timestamp and add it to the client challenge. Then we + * need to copy the challenge to the credential field in + * preparation for the next cycle. + */ + if (netr_validate_chain(netr_info, &arg.auth) == 0) { + /* + * Save the new password. + */ + (void) memcpy(netr_info->password, new_password, + NETR_OWF_PASSWORD_SZ); + } + + mlsvc_rpc_free(netr_handle->context, &heap); + return (0); +} + +/* + * netr_gen_password + * + * Generate a new pasword from the old password and the session key. + * The algorithm is a two stage hash. The session key is used in the + * first hash but only part of the session key is used in the second + * hash. + * + * If any difficulties occur using the cryptographic framework, the + * function returns SMBAUTH_FAILURE. Otherwise SMBAUTH_SUCCESS is + * returned. + */ +static int +netr_gen_password(BYTE *session_key, BYTE *old_password, BYTE *new_password) +{ + unsigned char partial_key[8]; + int rv; + + rv = smb_auth_DES(new_password, 8, session_key, 8, old_password, 8); + if (rv != SMBAUTH_SUCCESS) + return (rv); + + bzero(partial_key, 8); + partial_key[0] = session_key[7]; + + rv = smb_auth_DES(&new_password[8], 8, partial_key, 8, + &old_password[8], 8); + return (rv); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/netr_logon.c b/usr/src/lib/smbsrv/libmlsvc/common/netr_logon.c new file mode 100644 index 0000000000..d26d6f0dc7 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/netr_logon.c @@ -0,0 +1,584 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * NETR SamLogon and SamLogoff RPC client functions. + */ + +#include <stdio.h> +#include <strings.h> +#include <stdlib.h> +#include <time.h> +#include <alloca.h> +#include <unistd.h> +#include <netdb.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ndl/netlogon.ndl> +#include <smbsrv/mlsvc_util.h> +#include <smbsrv/mlsvc.h> +#include <smbsrv/netrauth.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/mlrpc.h> +#include <smbsrv/smb_token.h> + +extern int netr_open(char *server, char *domain, mlsvc_handle_t *netr_handle); +extern int netr_close(mlsvc_handle_t *netr_handle); +extern DWORD netlogon_auth(char *server, mlsvc_handle_t *netr_handle, + DWORD flags); +extern int netr_setup_authenticator(netr_info_t *, struct netr_authenticator *, + struct netr_authenticator *); +extern DWORD netr_validate_chain(netr_info_t *, struct netr_authenticator *); + +static DWORD netr_server_samlogon(mlsvc_handle_t *, netr_info_t *, char *, + netr_client_t *, smb_userinfo_t *); +static void netr_invalidate_chain(void); +static void netr_interactive_samlogon(netr_info_t *, netr_client_t *, + struct netr_logon_info1 *); +static void netr_network_samlogon(netr_info_t *, netr_client_t *, + netr_response_t *, netr_response_t *, struct netr_logon_info2 *); +static void netr_setup_identity(mlrpc_heap_t *, netr_client_t *, + netr_logon_id_t *); + +/* + * Shared with netr_auth.c + */ +extern netr_info_t netr_global_info; + +/* + * netlogon_logon + * + * This is the entry point for authenticating a remote logon. The + * parameters here all refer to the remote user and workstation, i.e. + * the domain is the user's account domain, not our primary domain. + * In order to make it easy to track which domain is being used at + * each stage, and to reduce the number of things being pushed on the + * stack, the client information is bundled up in the clnt structure. + * + * If the user is successfully authenticated, an access token will be + * built and NT_STATUS_SUCCESS will be returned. Otherwise a non-zero + * NT status will be returned, in which case the token contents will + * be invalid. + */ +DWORD +netlogon_logon(netr_client_t *clnt, smb_userinfo_t *user_info) +{ + char resource_domain[SMB_PI_MAX_DOMAIN]; + mlsvc_handle_t netr_handle; + smb_ntdomain_t *di; + DWORD status; + int retries = 0; + + smb_config_rdlock(); + (void) strlcpy(resource_domain, smb_config_getstr(SMB_CI_DOMAIN_NAME), + sizeof (resource_domain)); + smb_config_unlock(); + + /* + * If the SMB info cache is not valid, + * try to locate a domain controller. + */ + if ((di = smb_getdomaininfo(0)) == NULL) { + (void) mlsvc_locate_domain_controller(resource_domain); + + if ((di = smb_getdomaininfo(0)) == NULL) + return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + } + + if ((mlsvc_echo(di->server)) < 0) { + /* + * We had a session to the DC but it's not responding. + * So drop the credential chain and find another DC. + */ + netr_invalidate_chain(); + (void) mlsvc_locate_domain_controller(resource_domain); + + if ((di = smb_getdomaininfo(0)) == NULL) + return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + } + + do { + status = netr_open(di->server, di->domain, &netr_handle); + if (status != 0) + return (status); + + if ((netr_global_info.flags & NETR_FLG_VALID) == 0) { + status = netlogon_auth(di->server, &netr_handle, + NETR_FLG_NULL); + + if (status != 0) { + (void) netr_close(&netr_handle); + return (NT_STATUS_LOGON_FAILURE); + } + + netr_global_info.flags |= NETR_FLG_VALID; + } + + status = netr_server_samlogon(&netr_handle, + &netr_global_info, di->server, clnt, user_info); + + (void) netr_close(&netr_handle); + } while (status == NT_STATUS_INSUFFICIENT_LOGON_INFO && retries++ < 3); + + if (retries >= 3) + status = NT_STATUS_LOGON_FAILURE; + + return (status); +} + +static DWORD +netr_setup_userinfo(struct netr_validation_info3 *info3, + smb_userinfo_t *user_info, netr_client_t *clnt) +{ + smb_sid_attrs_t *other_grps; + char *username, *domain; + int i, nbytes; + + user_info->sid_name_use = SidTypeUser; + user_info->rid = info3->UserId; + user_info->primary_group_rid = info3->PrimaryGroupId; + user_info->domain_sid = nt_sid_dup((nt_sid_t *)info3->LogonDomainId); + + if (user_info->domain_sid == NULL) + return (NT_STATUS_NO_MEMORY); + + user_info->user_sid = nt_sid_splice(user_info->domain_sid, + user_info->rid); + if (user_info->user_sid == NULL) + return (NT_STATUS_NO_MEMORY); + + user_info->pgrp_sid = nt_sid_splice(user_info->domain_sid, + user_info->primary_group_rid); + if (user_info->pgrp_sid == NULL) + return (NT_STATUS_NO_MEMORY); + + username = (info3->EffectiveName.str) + ? (char *)info3->EffectiveName.str : clnt->username; + domain = (info3->LogonDomainName.str) + ? (char *)info3->LogonDomainName.str : clnt->domain; + + if (username) + user_info->name = strdup(username); + if (domain) + user_info->domain_name = strdup(domain); + + if (user_info->name == NULL || user_info->domain_name == NULL) + return (NT_STATUS_NO_MEMORY); + + nbytes = info3->GroupCount * sizeof (smb_rid_attrs_t); + if (nbytes) { + if ((user_info->groups = malloc(nbytes)) != NULL) { + user_info->n_groups = info3->GroupCount; + (void) memcpy(user_info->groups, + info3->GroupIds, nbytes); + } else { + return (NT_STATUS_NO_MEMORY); + } + } + + nbytes = info3->SidCount * sizeof (smb_sid_attrs_t); + if (nbytes) { + if ((other_grps = malloc(nbytes)) != NULL) { + user_info->other_grps = other_grps; + for (i = 0; i < info3->SidCount; i++) { + other_grps[i].attrs = + info3->ExtraSids[i].attributes; + + other_grps[i].sid = nt_sid_dup( + (nt_sid_t *)info3->ExtraSids[i].sid); + + if (other_grps[i].sid == NULL) + break; + } + user_info->n_other_grps = i; + } else { + return (NT_STATUS_NO_MEMORY); + } + } + + mlsvc_setadmin_user_info(user_info); + return (NT_STATUS_SUCCESS); +} + +/* + * netr_server_samlogon + * + * NetrServerSamLogon RPC: interactive or network. It is assumed that + * we have already authenticated with the PDC. If everything works, + * we build a user info structure and return it, where the caller will + * probably build an access token. + * + * Returns an NT status. There are numerous possibilities here. + * For example: + * NT_STATUS_INVALID_INFO_CLASS + * NT_STATUS_INVALID_PARAMETER + * NT_STATUS_ACCESS_DENIED + * NT_STATUS_PASSWORD_MUST_CHANGE + * NT_STATUS_NO_SUCH_USER + * NT_STATUS_WRONG_PASSWORD + * NT_STATUS_LOGON_FAILURE + * NT_STATUS_ACCOUNT_RESTRICTION + * NT_STATUS_INVALID_LOGON_HOURS + * NT_STATUS_INVALID_WORKSTATION + * NT_STATUS_INTERNAL_ERROR + * NT_STATUS_PASSWORD_EXPIRED + * NT_STATUS_ACCOUNT_DISABLED + */ +DWORD +netr_server_samlogon(mlsvc_handle_t *netr_handle, netr_info_t *netr_info, + char *server, netr_client_t *clnt, smb_userinfo_t *user_info) +{ + struct netr_SamLogon arg; + struct netr_authenticator auth; + struct netr_authenticator ret_auth; + struct netr_logon_info1 info1; + struct netr_logon_info2 info2; + struct netr_validation_info3 *info3; + netr_response_t nt_rsp; + netr_response_t lm_rsp; + mlrpc_heapref_t heap; + int opnum; + int rc, len; + DWORD status; + + bzero(&arg, sizeof (struct netr_SamLogon)); + opnum = NETR_OPNUM_SamLogon; + (void) mlsvc_rpc_init(&heap); + + /* + * Should we get the server and hostname from netr_info? + */ + len = strlen(server) + 4; + arg.servername = alloca(len); + (void) snprintf((char *)arg.servername, len, "\\\\%s", server); + + arg.hostname = alloca(MLSVC_DOMAIN_NAME_MAX); + rc = smb_gethostname((char *)arg.hostname, MLSVC_DOMAIN_NAME_MAX, 0); + if (rc != 0) { + mlrpc_heap_destroy(heap.heap); + return (NT_STATUS_INTERNAL_ERROR); + } + + rc = netr_setup_authenticator(netr_info, &auth, &ret_auth); + if (rc != SMBAUTH_SUCCESS) { + mlrpc_heap_destroy(heap.heap); + return (NT_STATUS_INTERNAL_ERROR); + } + + arg.auth = &auth; + arg.ret_auth = &ret_auth; + arg.validation_level = NETR_VALIDATION_LEVEL3; + arg.logon_info.logon_level = clnt->logon_level; + arg.logon_info.switch_value = clnt->logon_level; + + switch (clnt->logon_level) { + case NETR_INTERACTIVE_LOGON: + netr_setup_identity(heap.heap, clnt, &info1.identity); + netr_interactive_samlogon(netr_info, clnt, &info1); + arg.logon_info.ru.info1 = &info1; + break; + + case NETR_NETWORK_LOGON: + netr_setup_identity(heap.heap, clnt, &info2.identity); + netr_network_samlogon(netr_info, clnt, &nt_rsp, &lm_rsp, + &info2); + arg.logon_info.ru.info2 = &info2; + break; + + default: + mlrpc_heap_destroy(heap.heap); + return (NT_STATUS_INVALID_PARAMETER); + } + + rc = mlsvc_rpc_call(netr_handle->context, opnum, &arg, &heap); + if (rc != 0) { + bzero(netr_info, sizeof (netr_info_t)); + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.status != 0) { + status = NT_SC_VALUE(arg.status); + + /* + * We need to validate the chain even though we have + * a non-zero status. If the status is ACCESS_DENIED + * this will trigger a new credential chain. However, + * a valid credential is returned with some status + * codes; for example, WRONG_PASSWORD. + */ + (void) netr_validate_chain(netr_info, arg.ret_auth); + } else { + status = netr_validate_chain(netr_info, arg.ret_auth); + if (status == NT_STATUS_INSUFFICIENT_LOGON_INFO) { + mlsvc_rpc_free(netr_handle->context, &heap); + return (status); + } + + info3 = arg.ru.info3; + status = netr_setup_userinfo(info3, user_info, clnt); + } + + mlsvc_rpc_free(netr_handle->context, &heap); + return (status); +} + +/* + * netr_interactive_samlogon + * + * Set things up for an interactive SamLogon. Copy the NT and LM + * passwords to the logon structure and hash them with the session + * key. + */ +static void +netr_interactive_samlogon(netr_info_t *netr_info, netr_client_t *clnt, + struct netr_logon_info1 *info1) +{ + BYTE key[NETR_OWF_PASSWORD_SZ]; + + (void) memcpy(&info1->lm_owf_password, + clnt->lm_password.lm_password_val, sizeof (netr_owf_password_t)); + + (void) memcpy(&info1->nt_owf_password, + clnt->nt_password.nt_password_val, sizeof (netr_owf_password_t)); + + (void) memset(key, 0, NETR_OWF_PASSWORD_SZ); + (void) memcpy(key, netr_info->session_key, NETR_SESSION_KEY_SZ); + + rand_hash((unsigned char *)&info1->lm_owf_password, + NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ); + + rand_hash((unsigned char *)&info1->nt_owf_password, + NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ); +} + +/* + * netr_network_samlogon + * + * Set things up for a network SamLogon. We provide a copy of the random + * challenge, that we sent to the client, to the domain controller. This + * is the key that the client will have used to encrypt the NT and LM + * passwords. Note that Windows 9x clients may not provide both passwords. + */ +/*ARGSUSED*/ +static void +netr_network_samlogon(netr_info_t *netr_info, netr_client_t *clnt, + netr_response_t *ntr, netr_response_t *lmr, struct netr_logon_info2 *info2) +{ + bcopy(clnt->challenge_key.challenge_key_val, info2->lm_challenge.data, + 8); + + if (clnt->nt_password.nt_password_len == NETR_CR_PASSWORD_SIZE) { + ntr->length = NETR_CR_PASSWORD_SIZE; + ntr->start = 0; + ntr->max_length = NETR_CR_PASSWORD_SIZE; + bcopy(clnt->nt_password.nt_password_val, ntr->data, + NETR_CR_PASSWORD_SIZE); + + info2->nt_response.length = NETR_CR_PASSWORD_SIZE; + info2->nt_response.max_length = NETR_CR_PASSWORD_SIZE; + info2->nt_response.data = ntr; + } else { + info2->nt_response.length = 0; + info2->nt_response.max_length = 0; + info2->nt_response.data = 0; + } + + if (clnt->lm_password.lm_password_len == NETR_CR_PASSWORD_SIZE) { + lmr->length = NETR_CR_PASSWORD_SIZE; + lmr->start = 0; + lmr->max_length = NETR_CR_PASSWORD_SIZE; + bcopy(clnt->lm_password.lm_password_val, lmr->data, + NETR_CR_PASSWORD_SIZE); + + info2->lm_response.length = NETR_CR_PASSWORD_SIZE; + info2->lm_response.max_length = NETR_CR_PASSWORD_SIZE; + info2->lm_response.data = lmr; + } else { + info2->lm_response.length = 0; + info2->lm_response.max_length = 0; + info2->lm_response.data = 0; + } +} + +/* + * netr_setup_authenticator + * + * Set up the request and return authenticators. A new credential is + * generated from the session key, the current client credential and + * the current time, i.e. + * + * NewCredential = Cred(SessionKey, OldCredential, time); + * + * The timestamp, which is used as a random seed, is stored in both + * the request and return authenticators. + * + * If any difficulties occur using the cryptographic framework, the + * function returns SMBAUTH_FAILURE. Otherwise SMBAUTH_SUCCESS is + * returned. + */ +int +netr_setup_authenticator(netr_info_t *netr_info, + struct netr_authenticator *auth, struct netr_authenticator *ret_auth) +{ + bzero(auth, sizeof (struct netr_authenticator)); + +#ifdef _BIG_ENDIAN + netr_info->timestamp = 0; +#else + netr_info->timestamp = time(0) << 8; +#endif + auth->timestamp = netr_info->timestamp; + + if (netr_gen_credentials(netr_info->session_key, + &netr_info->client_credential, + netr_info->timestamp, + (netr_cred_t *)&auth->credential) != SMBAUTH_SUCCESS) + return (SMBAUTH_FAILURE); + + if (ret_auth) { + bzero(ret_auth, sizeof (struct netr_authenticator)); + ret_auth->timestamp = netr_info->timestamp; + } + + return (SMBAUTH_SUCCESS); +} + +/* + * Validate the returned credentials and update the credential chain. + * The server returns an updated client credential rather than a new + * server credential. The server uses (timestamp + 1) when generating + * the credential. + * + * Generate the new seed for the credential chain. The new seed is + * formed by adding (timestamp + 1) to the current client credential. + * The only quirk is the DWORD style addition. + * + * Returns NT_STATUS_INSUFFICIENT_LOGON_INFO if auth->credential is a + * NULL pointer. The Authenticator field of the SamLogon response packet + * sent by the Samba 3 PDC always return NULL pointer if the received + * SamLogon request is not immediately followed by the ServerReqChallenge + * and ServerAuthenticate2 requests. + * + * Returns NT_STATUS_SUCCESS if the server returned a valid credential. + * Otherwise we retirm NT_STATUS_UNSUCCESSFUL. + */ +DWORD +netr_validate_chain(netr_info_t *netr_info, struct netr_authenticator *auth) +{ + netr_cred_t cred; + DWORD result = NT_STATUS_SUCCESS; + DWORD *dwp; + + ++netr_info->timestamp; + + if (netr_gen_credentials(netr_info->session_key, + &netr_info->client_credential, + netr_info->timestamp, &cred) != SMBAUTH_SUCCESS) + return (NT_STATUS_INTERNAL_ERROR); + + if (&auth->credential == 0) { + /* + * If the validation fails, destroy the credential chain. + * This should trigger a new authentication chain. + */ + bzero(netr_info, sizeof (netr_info_t)); + return (NT_STATUS_INSUFFICIENT_LOGON_INFO); + } + + result = memcmp(&cred, &auth->credential, sizeof (netr_cred_t)); + if (result != 0) { + /* + * If the validation fails, destroy the credential chain. + * This should trigger a new authentication chain. + */ + bzero(netr_info, sizeof (netr_info_t)); + result = NT_STATUS_UNSUCCESSFUL; + } else { + /* + * Otherwise generate the next step in the chain. + */ + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + dwp = (DWORD *)&netr_info->client_credential; + dwp[0] += netr_info->timestamp; + + netr_info->flags |= NETR_FLG_VALID; + } + + return (result); +} + +/* + * netr_invalidate_chain + * + * Mark the credential chain as invalid so that it will be recreated + * on the next attempt. + */ +static void +netr_invalidate_chain(void) +{ + netr_global_info.flags &= ~NETR_FLG_VALID; +} + +/* + * netr_setup_identity + * + * Set up the client identity information. All of this information is + * specifically related to the client user and workstation attempting + * to access this system. It may not be in our primary domain. + * + * I don't know what logon_id is, it seems to be a unique identifier. + * Increment it before each use. + */ +static void +netr_setup_identity(mlrpc_heap_t *heap, netr_client_t *clnt, + netr_logon_id_t *identity) +{ + static DWORD logon_id; + + if (logon_id == 0) + logon_id = 0xDCD0; + + ++logon_id; + clnt->logon_id = logon_id; + + identity->parameter_control = 0; + identity->logon_id.LowPart = logon_id; + identity->logon_id.HighPart = 0; + + mlrpc_heap_mkvcs(heap, clnt->domain, + (mlrpc_vcbuf_t *)&identity->domain_name); + + mlrpc_heap_mkvcs(heap, clnt->username, + (mlrpc_vcbuf_t *)&identity->username); + + /* + * Some systems prefix the client workstation name with \\. + * It doesn't seem to make any difference whether it's there + * or not. + */ + mlrpc_heap_mkvcs(heap, clnt->workstation, + (mlrpc_vcbuf_t *)&identity->workstation); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/samlib.c b/usr/src/lib/smbsrv/libmlsvc/common/samlib.c new file mode 100644 index 0000000000..aa75c2678e --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/samlib.c @@ -0,0 +1,512 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module provides the high level interface to the SAM RPC + * functions. + */ + +#include <unistd.h> +#include <netdb.h> +#include <alloca.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libmlsvc.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/ntaccess.h> +#include <smbsrv/ntsid.h> +#include <smbsrv/lsalib.h> +#include <smbsrv/samlib.h> + +/* + * Valid values for the OEM OWF password encryption. + */ +#define SAM_PASSWORD_516 516 +#define SAM_KEYLEN 16 + +extern DWORD samr_set_user_info(mlsvc_handle_t *, smb_auth_info_t *); +static int get_user_group_info(mlsvc_handle_t *, smb_userinfo_t *); + +/* + * sam_lookup_user_info + * + * Lookup user information in the SAM database on the specified server + * (domain controller). The LSA interface is used to obtain the user + * RID, the domain name and the domain SID (user privileges are TBD). + * Then the various SAM layers are opened using the domain SID and the + * user RID to obtain the users's group membership information. + * + * The information is returned in the user_info structure. The caller + * is responsible for allocating and releasing this structure. If the + * lookup is successful, sid_name_use will be set to SidTypeUser. + * + * On success 0 is returned. Otherwise a -ve error code. + */ +int +sam_lookup_user_info(char *server, char *domain_name, + char *account_name, char *password, smb_userinfo_t *user_info) +{ + mlsvc_handle_t samr_handle; + mlsvc_handle_t domain_handle; + mlsvc_handle_t user_handle; + struct samr_sid *sid; + int rc; + DWORD access_mask; + DWORD status; + + if (lsa_lookup_name(server, domain_name, account_name, user_info) != 0) + return (-1); + + if (user_info->sid_name_use != SidTypeUser || + user_info->rid == 0 || user_info->domain_sid == 0) { + return (-1); + } + + rc = samr_open(MLSVC_IPC_USER, server, domain_name, account_name, + password, SAM_LOOKUP_INFORMATION, &samr_handle); + if (rc != 0) + return (-1); +#if 0 + rc = samr_lookup_domain(&samr_handle, domain_name, user_info); + if (rc != 0) + return (-1); +#endif + sid = (struct samr_sid *)user_info->domain_sid; + + status = samr_open_domain(&samr_handle, SAM_LOOKUP_INFORMATION, + sid, &domain_handle); + if (status == 0) { +#if 0 + (void) samr_lookup_domain_names(&domain_handle, account_name, + user_info); +#endif + access_mask = STANDARD_RIGHTS_EXECUTE | SAM_ACCESS_USER_READ; + + rc = samr_open_user(&domain_handle, access_mask, + user_info->rid, &user_handle); + + if (rc == 0) { + (void) get_user_group_info(&user_handle, user_info); + (void) samr_close_handle(&user_handle); + } + + (void) samr_close_handle(&domain_handle); + } + + (void) samr_close_handle(&samr_handle); + return (rc); +} + +/* + * get_user_group_info + * + * This is a private function to obtain the primary group and group + * memberships for the user specified by the user_handle. This function + * should only be called from sam_lookup_user_info. + * + * On success 0 is returned. Otherwise -1 is returned. + */ +static int +get_user_group_info(mlsvc_handle_t *user_handle, smb_userinfo_t *user_info) +{ + union samr_user_info sui; + int rc; + + rc = samr_query_user_info(user_handle, SAMR_QUERY_USER_GROUPRID, &sui); + if (rc != 0) + return (-1); + + rc = samr_query_user_groups(user_handle, user_info); + if (rc != 0) + return (-1); + + user_info->primary_group_rid = sui.info9.group_rid; + return (0); +} + +/* + * sam_create_trust_account + * + * Create a trust account for this system. + * + * SAMR_AF_WORKSTATION_TRUST_ACCOUNT: servers and workstations. + * SAMR_AF_SERVER_TRUST_ACCOUNT: domain controllers. + * + * Returns NT status codes. + */ +DWORD +sam_create_trust_account(char *server, char *domain, smb_auth_info_t *auth) +{ + smb_userinfo_t *user_info; + char account_name[MAXHOSTNAMELEN]; + DWORD status; + + if (smb_gethostname(account_name, MAXHOSTNAMELEN - 2, 1) != 0) + return (NT_STATUS_NO_MEMORY); + + (void) strlcat(account_name, "$", MAXHOSTNAMELEN); + + if ((user_info = mlsvc_alloc_user_info()) == 0) + return (NT_STATUS_NO_MEMORY); + + /* + * The trust account value here should match + * the value that will be used when the user + * information is set on this account. + */ + status = sam_create_account(server, domain, account_name, + auth, SAMR_AF_WORKSTATION_TRUST_ACCOUNT, user_info); + + mlsvc_free_user_info(user_info); + return (status); +} + + +/* + * sam_create_account + * + * Create the specified domain account in the SAM database on the + * domain controller. + * + * Account flags: + * SAMR_AF_NORMAL_ACCOUNT + * SAMR_AF_WORKSTATION_TRUST_ACCOUNT + * SAMR_AF_SERVER_TRUST_ACCOUNT + * + * Returns NT status codes. + */ +DWORD +sam_create_account(char *server, char *domain_name, char *account_name, + smb_auth_info_t *auth, DWORD account_flags, smb_userinfo_t *user_info) +{ + mlsvc_handle_t samr_handle; + mlsvc_handle_t domain_handle; + mlsvc_handle_t user_handle; + union samr_user_info sui; + struct samr_sid *sid; + DWORD rid; + DWORD status; + int rc; + + rc = samr_open(MLSVC_IPC_ADMIN, server, domain_name, 0, 0, + SAM_CONNECT_CREATE_ACCOUNT, &samr_handle); + + if (rc != 0) { + status = NT_STATUS_OPEN_FAILED; + smb_tracef("SamCreateAccount[%s\\%s]: %s", + domain_name, account_name, xlate_nt_status(status)); + return (status); + } + + if (samr_handle.context->server_os == NATIVE_OS_WIN2000) { + nt_domain_t *ntdp; + + if ((ntdp = nt_domain_lookup_name(domain_name)) == 0) { + (void) lsa_query_account_domain_info(); + if ((ntdp = nt_domain_lookup_name(domain_name)) == 0) { + (void) samr_close_handle(&samr_handle); + status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + smb_tracef("SamCreateAccount[%s\\%s]: %s", + domain_name, account_name, + xlate_nt_status(status)); + return (status); + } + } + + sid = (struct samr_sid *)ntdp->sid; + } else { + if (samr_lookup_domain(&samr_handle, + domain_name, user_info) != 0) { + (void) samr_close_handle(&samr_handle); + smb_tracef("SamCreateAccount[%s]: lookup failed", + account_name); + + return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + } + + sid = (struct samr_sid *)user_info->domain_sid; + } + + status = samr_open_domain(&samr_handle, + SAM_DOMAIN_CREATE_ACCOUNT, sid, &domain_handle); + + if (status == NT_STATUS_SUCCESS) { + status = samr_create_user(&domain_handle, account_name, + account_flags, &rid, &user_handle); + + if (status == NT_STATUS_SUCCESS) { + (void) samr_query_user_info(&user_handle, + SAMR_QUERY_USER_UNKNOWN16, &sui); + + (void) samr_get_user_pwinfo(&user_handle); + (void) samr_set_user_info(&user_handle, auth); + (void) samr_close_handle(&user_handle); + } else if (status == NT_STATUS_USER_EXISTS) { + mlsvc_release_user_info(user_info); + + rc = lsa_lookup_name(server, domain_name, account_name, + user_info); + if (rc == 0) + rid = user_info->rid; + status = 0; + } else { + smb_tracef("SamCreateAccount[%s]: %s", + account_name, xlate_nt_status(status)); + } + + (void) samr_close_handle(&domain_handle); + } else { + smb_tracef("SamCreateAccount[%s]: open domain failed", + account_name); + status = (NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + } + + (void) samr_close_handle(&samr_handle); + return (status); +} + + +/* + * sam_remove_trust_account + * + * Attempt to remove the workstation trust account for this system. + * Administrator access is required to perform this operation. + * + * Returns NT status codes. + */ +DWORD +sam_remove_trust_account(char *server, char *domain) +{ + char account_name[MAXHOSTNAMELEN]; + + if (smb_gethostname(account_name, MAXHOSTNAMELEN - 2, 1) != 0) + return (NT_STATUS_NO_MEMORY); + + (void) strcat(account_name, "$"); + + return (sam_delete_account(server, domain, account_name)); +} + + +/* + * sam_delete_account + * + * Attempt to remove an account from the SAM database on the specified + * server. + * + * Returns NT status codes. + */ +DWORD +sam_delete_account(char *server, char *domain_name, char *account_name) +{ + mlsvc_handle_t samr_handle; + mlsvc_handle_t domain_handle; + mlsvc_handle_t user_handle; + smb_userinfo_t *user_info; + struct samr_sid *sid; + DWORD rid; + DWORD access_mask; + DWORD status; + int rc; + + if ((user_info = mlsvc_alloc_user_info()) == 0) + return (NT_STATUS_NO_MEMORY); + + rc = samr_open(MLSVC_IPC_ADMIN, server, domain_name, 0, 0, + SAM_LOOKUP_INFORMATION, &samr_handle); + + if (rc != 0) { + mlsvc_free_user_info(user_info); + return (NT_STATUS_OPEN_FAILED); + } + + if (samr_handle.context->server_os == NATIVE_OS_WIN2000) { + nt_domain_t *ntdp; + + if ((ntdp = nt_domain_lookup_name(domain_name)) == 0) { + (void) lsa_query_account_domain_info(); + if ((ntdp = nt_domain_lookup_name(domain_name)) == 0) { + + (void) samr_close_handle(&samr_handle); + return (NT_STATUS_NO_SUCH_DOMAIN); + } + } + + sid = (struct samr_sid *)ntdp->sid; + } else { + if (samr_lookup_domain( + &samr_handle, domain_name, user_info) != 0) { + (void) samr_close_handle(&samr_handle); + mlsvc_free_user_info(user_info); + return (NT_STATUS_NO_SUCH_DOMAIN); + } + + sid = (struct samr_sid *)user_info->domain_sid; + } + + status = samr_open_domain(&samr_handle, SAM_LOOKUP_INFORMATION, + sid, &domain_handle); + if (status == 0) { + mlsvc_release_user_info(user_info); + status = samr_lookup_domain_names(&domain_handle, + account_name, user_info); + + if (status == 0) { + rid = user_info->rid; + access_mask = STANDARD_RIGHTS_EXECUTE | DELETE; + + rc = samr_open_user(&domain_handle, access_mask, + rid, &user_handle); + if (rc == 0) { + if (samr_delete_user(&user_handle) != 0) + (void) samr_close_handle(&user_handle); + } + } + + (void) samr_close_handle(&domain_handle); + } + + (void) samr_close_handle(&samr_handle); + mlsvc_free_user_info(user_info); + return (status); +} + +/* + * sam_lookup_name + * + * Lookup an account name in the SAM database on the specified domain + * controller. Provides the account RID on success. + * + * Returns NT status codes. + */ +DWORD +sam_lookup_name(char *server, char *domain_name, char *account_name, + DWORD *rid_ret) +{ + mlsvc_handle_t samr_handle; + mlsvc_handle_t domain_handle; + smb_userinfo_t *user_info; + struct samr_sid *domain_sid; + int rc; + DWORD status; + + *rid_ret = 0; + + if ((user_info = mlsvc_alloc_user_info()) == 0) + return (NT_STATUS_NO_MEMORY); + + rc = samr_open(MLSVC_IPC_ANON, server, domain_name, 0, 0, + SAM_LOOKUP_INFORMATION, &samr_handle); + + if (rc != 0) { + mlsvc_free_user_info(user_info); + return (NT_STATUS_OPEN_FAILED); + } + + rc = samr_lookup_domain(&samr_handle, domain_name, user_info); + if (rc != 0) { + (void) samr_close_handle(&samr_handle); + mlsvc_free_user_info(user_info); + return (NT_STATUS_NO_SUCH_DOMAIN); + } + + domain_sid = (struct samr_sid *)user_info->domain_sid; + + status = samr_open_domain(&samr_handle, SAM_LOOKUP_INFORMATION, + domain_sid, &domain_handle); + if (status == 0) { + mlsvc_release_user_info(user_info); + + status = samr_lookup_domain_names(&domain_handle, + account_name, user_info); + if (status == 0) + *rid_ret = user_info->rid; + + (void) samr_close_handle(&domain_handle); + } + + (void) samr_close_handle(&samr_handle); + mlsvc_free_user_info(user_info); + return (status); +} + + +/* + * sam_get_local_domains + * + * Query a remote server to get the list of local domains that it + * supports. + * + * Returns NT status codes. + */ +DWORD +sam_get_local_domains(char *server, char *domain_name) +{ + mlsvc_handle_t samr_handle; + DWORD status; + int rc; + + rc = samr_open(MLSVC_IPC_ANON, server, domain_name, 0, 0, + SAM_ENUM_LOCAL_DOMAIN, &samr_handle); + if (rc != 0) + return (NT_STATUS_OPEN_FAILED); + + status = samr_enum_local_domains(&samr_handle); + (void) samr_close_handle(&samr_handle); + return (status); +} + +/* + * sam_oem_password + * + * Generate an OEM password. + */ +int sam_oem_password(oem_password_t *oem_password, unsigned char *new_password, + unsigned char *old_password) +{ + mts_wchar_t *unicode_password; + int length; + +#ifdef PBSHORTCUT + assert(sizeof (oem_password_t) == SAM_PASSWORD_516); +#endif /* PBSHORTCUT */ + + length = strlen((char const *)new_password); + unicode_password = alloca((length + 1) * sizeof (mts_wchar_t)); + + length = smb_auth_qnd_unicode((unsigned short *)unicode_password, + (char *)new_password, length); + oem_password->length = length; + + (void) memcpy(&oem_password->data[512 - length], + unicode_password, length); + + rand_hash((unsigned char *)oem_password, sizeof (oem_password_t), + old_password, SAM_KEYLEN); + + return (0); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/samr_lookup.c b/usr/src/lib/smbsrv/libmlsvc/common/samr_lookup.c new file mode 100644 index 0000000000..7eccd83e22 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/samr_lookup.c @@ -0,0 +1,565 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Security Access Manager RPC (SAMR) library interface functions for + * query and lookup calls. + */ + + +#include <stdio.h> +#include <strings.h> +#include <stdlib.h> +#include <unistd.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/ntsid.h> +#include <smbsrv/samlib.h> +#include <smbsrv/mlrpc.h> +#include <smbsrv/mlsvc.h> + +static int samr_setup_user_info(WORD, struct samr_QueryUserInfo *, + union samr_user_info *); +static void samr_set_user_unknowns(struct samr_SetUserInfo23 *); +static void samr_set_user_logon_hours(struct samr_SetUserInfo *); +static int samr_set_user_password(smb_auth_info_t *, BYTE *); + +/* + * samr_lookup_domain + * + * Lookup up the domain SID for the specified domain name. The handle + * should be one returned from samr_connect. The results will be + * returned in user_info - which should have been allocated by the + * caller. On success sid_name_use will be set to SidTypeDomain. + * + * Returns 0 on success, otherwise returns -ve error code. + */ +int +samr_lookup_domain(mlsvc_handle_t *samr_handle, char *domain_name, + smb_userinfo_t *user_info) +{ + struct samr_LookupDomain arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + size_t length; + + if (mlsvc_is_null_handle(samr_handle) || + domain_name == NULL || user_info == NULL) { + return (-1); + } + + context = samr_handle->context; + opnum = SAMR_OPNUM_LookupDomain; + bzero(&arg, sizeof (struct samr_LookupDomain)); + + (void) memcpy(&arg.handle, &samr_handle->handle, + sizeof (samr_handle_t)); + + length = mts_wcequiv_strlen(domain_name); + if (context->server_os == NATIVE_OS_WIN2000) + length += sizeof (mts_wchar_t); + + arg.domain_name.length = length; + arg.domain_name.allosize = length; + arg.domain_name.str = (unsigned char *)domain_name; + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + user_info->sid_name_use = SidTypeDomain; + user_info->domain_sid = nt_sid_dup((nt_sid_t *)arg.sid); + user_info->domain_name = MEM_STRDUP("mlrpc", domain_name); + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * samr_enum_local_domains + * + * Get the list of local domains supported by a server. + * + * Returns NT status codes. + */ +DWORD +samr_enum_local_domains(mlsvc_handle_t *samr_handle) +{ + struct samr_EnumLocalDomain arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + DWORD status; + + if (mlsvc_is_null_handle(samr_handle)) + return (NT_STATUS_INVALID_PARAMETER); + + context = samr_handle->context; + opnum = SAMR_OPNUM_EnumLocalDomains; + bzero(&arg, sizeof (struct samr_EnumLocalDomain)); + + (void) memcpy(&arg.handle, &samr_handle->handle, + sizeof (samr_handle_t)); + arg.enum_context = 0; + arg.max_length = 0x00002000; /* Value used by NT */ + + (void) mlsvc_rpc_init(&heap); + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else { + status = NT_SC_VALUE(arg.status); + + /* + * Handle none-mapped status quietly. + */ + if (status != NT_STATUS_NONE_MAPPED) + mlsvc_rpc_report_status(opnum, arg.status); + } + + return (status); +} + +/* + * samr_lookup_domain_names + * + * Lookup up a name + * returned in user_info - which should have been allocated by the + * caller. On success sid_name_use will be set to SidTypeDomain. + * + * Returns 0 on success. Otherwise returns an NT status code. + */ +DWORD +samr_lookup_domain_names(mlsvc_handle_t *domain_handle, char *name, + smb_userinfo_t *user_info) +{ + struct samr_LookupNames arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + DWORD status; + size_t length; + + if (mlsvc_is_null_handle(domain_handle) || + name == NULL || user_info == NULL) { + return (NT_STATUS_INVALID_PARAMETER); + } + + context = domain_handle->context; + opnum = SAMR_OPNUM_LookupNames; + bzero(&arg, sizeof (struct samr_LookupNames)); + + (void) memcpy(&arg.handle, &domain_handle->handle, + sizeof (samr_handle_t)); + arg.n_entry = 1; + arg.max_n_entry = 1000; + arg.index = 0; + arg.total = 1; + + length = mts_wcequiv_strlen(name); + if (context->server_os == NATIVE_OS_WIN2000) + length += sizeof (mts_wchar_t); + + arg.name.length = length; + arg.name.allosize = length; + arg.name.str = (unsigned char *)name; + + (void) mlsvc_rpc_init(&heap); + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.status != 0) { + status = NT_SC_VALUE(arg.status); + + /* + * Handle none-mapped status quietly. + */ + if (status != NT_STATUS_NONE_MAPPED) + mlsvc_rpc_report_status(opnum, arg.status); + } else { + user_info->name = MEM_STRDUP("mlrpc", name); + user_info->sid_name_use = arg.rid_types.rid_type[0]; + user_info->rid = arg.rids.rid[0]; + status = 0; + } + + mlsvc_rpc_free(context, &heap); + return (status); +} + +/* + * samr_query_user_info + * + * Query information on a specific user. The handle must be a valid + * user handle obtained via samr_open_user. + * + * Returns 0 on success, otherwise returns -ve error code. + */ +int +samr_query_user_info(mlsvc_handle_t *user_handle, WORD switch_value, + union samr_user_info *user_info) +{ + struct samr_QueryUserInfo arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + + if (mlsvc_is_null_handle(user_handle) || user_info == 0) + return (-1); + + context = user_handle->context; + opnum = SAMR_OPNUM_QueryUserInfo; + bzero(&arg, sizeof (struct samr_QueryUserInfo)); + + (void) memcpy(&arg.user_handle, &user_handle->handle, + sizeof (samr_handle_t)); + arg.switch_value = switch_value; + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) + rc = -1; + else + rc = samr_setup_user_info(switch_value, &arg, + user_info); + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + + +/* + * samr_setup_user_info + * + * Private function to set up the samr_user_info data. Dependent on + * the switch value this function may use strdup which will malloc + * memory. The caller is responsible for deallocating this memory. + * + * Returns 0 on success, otherwise returns -1. + */ +static int samr_setup_user_info(WORD switch_value, + struct samr_QueryUserInfo *arg, union samr_user_info *user_info) +{ + struct samr_QueryUserInfo1 *info1; + struct samr_QueryUserInfo6 *info6; + + switch (switch_value) { + case 1: + info1 = &arg->ru.info1; + user_info->info1.username = strdup( + (char const *)info1->username.str); + user_info->info1.fullname = strdup( + (char const *)info1->fullname.str); + user_info->info1.description = strdup( + (char const *)info1->description.str); + user_info->info1.unknown = 0; + user_info->info1.group_rid = info1->group_rid; + return (0); + + case 6: + info6 = &arg->ru.info6; + user_info->info6.username = strdup( + (char const *)info6->username.str); + user_info->info6.fullname = strdup( + (char const *)info6->fullname.str); + return (0); + + case 7: + user_info->info7.username = strdup( + (char const *)arg->ru.info7.username.str); + return (0); + + case 8: + user_info->info8.fullname = strdup( + (char const *)arg->ru.info8.fullname.str); + return (0); + + case 9: + user_info->info9.group_rid = arg->ru.info9.group_rid; + return (0); + + case 16: + return (0); + + default: + break; + }; + + return (-1); +} + +/* + * samr_query_user_groups + * + * Query the groups for a specific user. The handle must be a valid + * user handle obtained via samr_open_user. The list of groups is + * returned in group_info. Note that group_info->groups is allocated + * using malloc. The caller is responsible for deallocating this + * memory when it is no longer required. If group_info->n_entry is 0 + * then no memory was allocated. + * + * Returns 0 on success, otherwise returns -1. + */ +int +samr_query_user_groups(mlsvc_handle_t *user_handle, smb_userinfo_t *user_info) +{ + struct samr_QueryUserGroups arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + int nbytes; + + if (mlsvc_is_null_handle(user_handle) || user_info == NULL) + return (-1); + + context = user_handle->context; + opnum = SAMR_OPNUM_QueryUserGroups; + bzero(&arg, sizeof (struct samr_QueryUserGroups)); + + (void) memcpy(&arg.user_handle, &user_handle->handle, + sizeof (samr_handle_t)); + + (void) mlsvc_rpc_init(&heap); + + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.info == 0) { + rc = -1; + } else { + nbytes = arg.info->n_entry * + sizeof (struct samr_UserGroups); + user_info->groups = malloc(nbytes); + + if (user_info->groups == NULL) { + user_info->n_groups = 0; + rc = -1; + } else { + user_info->n_groups = arg.info->n_entry; + (void) memcpy(user_info->groups, + arg.info->groups, nbytes); + } + } + } + mlsvc_rpc_free(context, &heap); + return (rc); +} + + +/* + * samr_get_user_pwinfo + * + * Get some user password info. I'm not sure what this is yet but it is + * part of the create user sequence. The handle must be a valid user + * handle. Since I don't know what this is returning, I haven't provided + * any return data yet. + * + * Returns 0 on success. Otherwise returns an NT status code. + */ +DWORD +samr_get_user_pwinfo(mlsvc_handle_t *user_handle) +{ + struct samr_GetUserPwInfo arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + DWORD status; + + if (mlsvc_is_null_handle(user_handle)) + return (NT_STATUS_INVALID_PARAMETER); + + context = user_handle->context; + opnum = SAMR_OPNUM_GetUserPwInfo; + bzero(&arg, sizeof (struct samr_GetUserPwInfo)); + (void) memcpy(&arg.user_handle, &user_handle->handle, + sizeof (samr_handle_t)); + + (void) mlsvc_rpc_init(&heap); + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + status = NT_SC_VALUE(arg.status); + } else { + status = 0; + } + + mlsvc_rpc_free(context, &heap); + return (status); +} + + +/* + * samr_set_user_info + * + * Returns 0 on success. Otherwise returns an NT status code. + * NT status codes observed so far: + * NT_STATUS_WRONG_PASSWORD + */ +/*ARGSUSED*/ +DWORD +samr_set_user_info(mlsvc_handle_t *user_handle, smb_auth_info_t *auth) +{ + struct samr_SetUserInfo arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + DWORD status = 0; + + if (mlsvc_is_null_handle(user_handle)) + return (NT_STATUS_INVALID_PARAMETER); + + (void) mlsvc_rpc_init(&heap); + + context = user_handle->context; + opnum = SAMR_OPNUM_SetUserInfo; + bzero(&arg, sizeof (struct samr_SetUserInfo)); + (void) memcpy(&arg.user_handle, &user_handle->handle, + sizeof (samr_handle_t)); + + arg.info.index = SAMR_SET_USER_INFO_23; + arg.info.switch_value = SAMR_SET_USER_INFO_23; + + samr_set_user_unknowns(&arg.info.ru.info23); + samr_set_user_logon_hours(&arg); + + if (samr_set_user_password(auth, arg.info.ru.info23.password) < 0) + status = NT_STATUS_INTERNAL_ERROR; + + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + status = NT_SC_VALUE(arg.status); + } + + mlsvc_rpc_free(context, &heap); + return (status); +} + +static void +samr_set_user_unknowns(struct samr_SetUserInfo23 *info) +{ + bzero(info, sizeof (struct samr_SetUserInfo23)); + + info->sd.length = 0; + info->sd.data = 0; + info->user_rid = 0; + info->group_rid = MLSVC_DOMAIN_GROUP_RID_USERS; + + /* + * The trust account value used here should probably + * match the one used to create the trust account. + */ + info->acct_info = SAMR_AF_WORKSTATION_TRUST_ACCOUNT; + info->flags = 0x09F827FA; +} + +/* + * samr_set_user_logon_hours + * + * SamrSetUserInfo appears to contain some logon hours information, which + * looks like a varying, conformant array. The top level contains a value + * (units), which probably indicates the how to interpret the array. The + * array definition looks like it contains a maximum size, an initial + * offset and a bit length (units/8), followed by the bitmap. + * + * (info) + * +-------+ + * | units | + * +-------+ (hours) + * | hours |-->+-----------+ + * +-------+ | max_is | + * +-----------+ + * | first_is | + * +-----------+ + * | length_is | + * +------------------------+ + * | bitmap[length_is] | + * +---------+--------------+ + * + * 10080 minutes/week => 10080/8 = 1260 (0x04EC) bytes. + * 168 hours/week => 168/8 = 21 (0xA8) bytes. + * In the netmon examples seen so far, all bits are set to 1, i.e. + * an array containing 0xff. This is probably the default setting. + * + * ndrgen has a problem with complex [size_is] statements (length/8). + * So, for now, we fake it using two separate components. + */ +static void +samr_set_user_logon_hours(struct samr_SetUserInfo *sui) +{ + sui->logon_hours.size = SAMR_HOURS_MAX_SIZE; + sui->logon_hours.first = 0; + sui->logon_hours.length = SAMR_SET_USER_HOURS_SZ; + (void) memset(sui->logon_hours.bitmap, 0xFF, SAMR_SET_USER_HOURS_SZ); + + sui->info.ru.info23.logon_info.units = SAMR_HOURS_PER_WEEK; + sui->info.ru.info23.logon_info.hours = (DWORD)sui->logon_hours.bitmap; +} + +/* + * samr_set_user_password + * + * Set the initial password for the user. + * + * Returns 0 if everything goes well, -1 if there is trouble generating a + * key. + */ +static int +samr_set_user_password(smb_auth_info_t *auth, BYTE *oem_password) +{ + unsigned char nt_key[SMBAUTH_SESSION_KEY_SZ]; + char hostname[64]; + + randomize((char *)oem_password, SAMR_SET_USER_DATA_SZ); + + /* + * The new password is going to be + * the hostname in lower case. + */ + if (smb_gethostname(hostname, 64, 0) != 0) + return (-1); + + (void) utf8_strlwr(hostname); + + if (smb_auth_gen_session_key(auth, nt_key) != SMBAUTH_SUCCESS) + return (-1); + + /* + * Generate the OEM password from the hostname and the user session + * key(nt_key). + */ + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + (void) sam_oem_password((oem_password_t *)oem_password, + (unsigned char *)hostname, nt_key); + return (0); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/samr_open.c b/usr/src/lib/smbsrv/libmlsvc/common/samr_open.c new file mode 100644 index 0000000000..38dca838cd --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/samr_open.c @@ -0,0 +1,684 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Security Access Manager RPC (SAMR) library interface functions for + * connect, open and close calls. The SAM is a hierarchical database. + * If you want to talk to the SAM you need a SAM handle, if you want + * to work with a domain, you need to use the SAM handle to obtain a + * domain handle. Then you can use the domain handle to obtain a user + * handle etc. Be careful about returning null handles to the + * application. Use of a null handle may crash the domain controller + * if you attempt to use it. + */ + +#include <stdio.h> +#include <strings.h> +#include <unistd.h> +#include <netdb.h> +#include <sys/param.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libsmbrdr.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/ntaccess.h> +#include <smbsrv/samlib.h> +#include <smbsrv/mlrpc.h> +#include <smbsrv/mlsvc.h> + +/*LINTED E_STATIC_UNUSED*/ +static DWORD samr_connect1(char *, char *, char *, DWORD, mlsvc_handle_t *); +static DWORD samr_connect2(char *, char *, char *, DWORD, mlsvc_handle_t *); +static DWORD samr_connect3(char *, char *, char *, DWORD, mlsvc_handle_t *); +static DWORD samr_connect4(char *, char *, char *, DWORD, mlsvc_handle_t *); + +/* + * samr_open + * + * This is a wrapper round samr_connect to ensure that we connect using + * the appropriate session and logon. We default to the resource domain + * information if the caller doesn't supply a server name and a domain + * name. We store the remote server's native OS type - we may need it + * due to differences between platforms like NT and Windows 2000. + * + * On success 0 is returned. Otherwise a -ve error code. + */ +int +samr_open(int ipc_mode, char *server, char *domain, char *username, + char *password, DWORD access_mask, mlsvc_handle_t *samr_handle) +{ + smb_ntdomain_t *di; + int remote_os; + int remote_lm; + int rc; + + if ((di = smb_getdomaininfo(0)) == NULL) + return (-1); + + if (server == NULL || domain == NULL) { + server = di->server; + domain = di->domain; + } + + switch (ipc_mode) { + case MLSVC_IPC_USER: + /* + * Use the supplied credentials. + */ + rc = mlsvc_user_logon(server, domain, username, password); + break; + + case MLSVC_IPC_ADMIN: + /* + * Use the resource domain administrator credentials. + */ + server = di->server; + domain = di->domain; + username = smbrdr_ipc_get_user(); + + rc = mlsvc_admin_logon(server, domain); + break; + + case MLSVC_IPC_ANON: + default: + rc = mlsvc_anonymous_logon(server, domain, &username); + break; + } + + if (rc != 0) + return (-1); + + rc = samr_connect(server, domain, username, access_mask, samr_handle); + if (rc == 0) { + (void) mlsvc_session_native_values(samr_handle->context->fid, + &remote_os, &remote_lm, 0); + samr_handle->context->server_os = remote_os; + } + return (rc); +} + + +/* + * samr_connect + * + * Connect to the SAM on the specified server (domain controller). + * This is the entry point for the various SAM connect calls. We do + * parameter validation and open the samr named pipe here. The actual + * RPC is based on the native OS of the server. + * + * Returns 0 on success. Otherwise returns a -ve error code. + */ +int +samr_connect(char *server, char *domain, char *username, DWORD access_mask, + mlsvc_handle_t *samr_handle) +{ + DWORD status; + int remote_os; + int remote_lm; + int fid; + int rc = 0; + + if (server == NULL || domain == NULL || + username == NULL || samr_handle == NULL) + return (-1); + + if ((fid = mlsvc_open_pipe(server, domain, username, "\\samr")) < 0) + return (-1); + + if (mlsvc_rpc_bind(samr_handle, fid, "SAMR") < 0) { + (void) mlsvc_close_pipe(fid); + return (-1); + } + + (void) mlsvc_session_native_values(fid, &remote_os, &remote_lm, 0); + + switch (remote_os) { + case NATIVE_OS_NT5_1: + status = samr_connect4(server, domain, username, access_mask, + samr_handle); + break; + + case NATIVE_OS_NT5_0: + status = samr_connect3(server, domain, username, access_mask, + samr_handle); + break; + + case NATIVE_OS_NT4_0: + default: + status = samr_connect2(server, domain, username, access_mask, + samr_handle); + break; + } + + if (status != NT_STATUS_SUCCESS) { + (void) mlsvc_close_pipe(fid); + free(samr_handle->context); + rc = -1; + } + return (rc); +} + +/* + * samr_connect1 + * + * Original SAMR connect call; probably used on Windows NT 3.51. + * Windows 95 uses this call with the srvmgr tools update. + * Servername appears to be a dword rather than a string. + * The first word contains '\' and the second word contains 0x001, + * (which is probably uninitialized junk: 0x0001005c. + */ +/*ARGSUSED*/ +static DWORD +samr_connect1(char *server, char *domain, char *username, DWORD access_mask, + mlsvc_handle_t *samr_handle) +{ + struct samr_ConnectAnon arg; + mlrpc_heapref_t heapref; + int opnum; + DWORD status; + + bzero(&arg, sizeof (struct samr_ConnectAnon)); + opnum = SAMR_OPNUM_ConnectAnon; + status = NT_STATUS_SUCCESS; + + (void) mlsvc_rpc_init(&heapref); + arg.servername = (DWORD *)mlrpc_heap_malloc(heapref.heap, + sizeof (DWORD)); + *(arg.servername) = 0x0001005c; + arg.access_mask = access_mask; + + if (mlsvc_rpc_call(samr_handle->context, opnum, &arg, &heapref) != 0) { + status = NT_STATUS_UNSUCCESSFUL; + } else if (arg.status != 0) { + status = NT_SC_VALUE(arg.status); + } else { + (void) memcpy(&samr_handle->handle, &arg.handle, + sizeof (ms_handle_t)); + + if (mlsvc_is_null_handle(samr_handle)) + status = NT_STATUS_INVALID_HANDLE; + } + + mlsvc_rpc_free(samr_handle->context, &heapref); + return (status); +} + +/* + * samr_connect2 + * + * Connect to the SAM on a Windows NT 4.0 server (domain controller). + * We need the domain controller name and, if everything works, we + * return a handle. This function adds the double backslash prefx to + * make it easy for applications. + * + * Returns 0 on success. Otherwise returns a -ve error code. + */ +/*ARGSUSED*/ +static DWORD +samr_connect2(char *server, char *domain, char *username, DWORD access_mask, + mlsvc_handle_t *samr_handle) +{ + struct samr_Connect arg; + mlrpc_heapref_t heapref; + int opnum; + DWORD status; + int len; + + bzero(&arg, sizeof (struct samr_Connect)); + opnum = SAMR_OPNUM_Connect; + status = NT_STATUS_SUCCESS; + + (void) mlsvc_rpc_init(&heapref); + len = strlen(server) + 4; + arg.servername = mlrpc_heap_malloc(heapref.heap, len); + (void) snprintf((char *)arg.servername, len, "\\\\%s", server); + arg.access_mask = access_mask; + + if (mlsvc_rpc_call(samr_handle->context, opnum, &arg, &heapref) != 0) { + status = NT_STATUS_UNSUCCESSFUL; + } else if (arg.status != 0) { + status = NT_SC_VALUE(arg.status); + } else { + (void) memcpy(&samr_handle->handle, &arg.handle, + sizeof (ms_handle_t)); + + if (mlsvc_is_null_handle(samr_handle)) + status = NT_STATUS_INVALID_HANDLE; + } + + mlsvc_rpc_free(samr_handle->context, &heapref); + return (status); +} + +/* + * samr_connect3 + * + * Connect to the SAM on a Windows 2000 domain controller. + */ +/*ARGSUSED*/ +static DWORD +samr_connect3(char *server, char *domain, char *username, DWORD access_mask, + mlsvc_handle_t *samr_handle) +{ + struct samr_Connect3 arg; + mlrpc_heapref_t heapref; + int opnum; + DWORD status; + int len; + + bzero(&arg, sizeof (struct samr_Connect3)); + opnum = SAMR_OPNUM_Connect3; + status = NT_STATUS_SUCCESS; + + (void) mlsvc_rpc_init(&heapref); + len = strlen(server) + 4; + arg.servername = mlrpc_heap_malloc(heapref.heap, len); + (void) snprintf((char *)arg.servername, len, "\\\\%s", server); + arg.unknown_02 = 0x00000002; + arg.access_mask = access_mask; + + if (mlsvc_rpc_call(samr_handle->context, opnum, &arg, &heapref) != 0) { + status = NT_STATUS_UNSUCCESSFUL; + } else if (arg.status != 0) { + status = NT_SC_VALUE(arg.status); + } else { + (void) memcpy(&samr_handle->handle, &arg.handle, + sizeof (ms_handle_t)); + + if (mlsvc_is_null_handle(samr_handle)) + status = NT_STATUS_INVALID_HANDLE; + } + + mlsvc_rpc_free(samr_handle->context, &heapref); + return (status); +} + +/* + * samr_connect4 + * + * Connect to the SAM on a Windows XP domain controller. On Windows + * XP, the server should be the fully qualified DNS domain name with + * a double backslash prefix. At this point, it is assumed that we + * need to add the prefix and the DNS domain name here. + * + * If this call succeeds, a SAMR handle is placed in samr_handle and + * zero is returned. Otherwise, a -ve error code is returned. + */ +/*ARGSUSED*/ +static DWORD +samr_connect4(char *server, char *domain, char *username, DWORD access_mask, + mlsvc_handle_t *samr_handle) +{ + struct samr_Connect4 arg; + mlrpc_heapref_t heapref; + char *dns_name; + int len; + int opnum; + DWORD status; + + bzero(&arg, sizeof (struct samr_Connect4)); + opnum = SAMR_OPNUM_Connect; + status = NT_STATUS_SUCCESS; + + (void) mlsvc_rpc_init(&heapref); + dns_name = mlrpc_heap_malloc(heapref.heap, MAXHOSTNAMELEN); + (void) smb_getdomainname(dns_name, MAXHOSTNAMELEN); + + if (strlen(dns_name) > 0) { + len = strlen(server) + strlen(dns_name) + 4; + arg.servername = mlrpc_heap_malloc(heapref.heap, len); + (void) snprintf((char *)arg.servername, len, "\\\\%s.%s", + server, dns_name); + } else { + len = strlen(server) + 4; + arg.servername = mlrpc_heap_malloc(heapref.heap, len); + (void) snprintf((char *)arg.servername, len, "\\\\%s", server); + } + + arg.access_mask = SAM_ENUM_LOCAL_DOMAIN; + arg.unknown2_00000001 = 0x00000001; + arg.unknown3_00000001 = 0x00000001; + arg.unknown4_00000003 = 0x00000003; + arg.unknown5_00000000 = 0x00000000; + + if (mlsvc_rpc_call(samr_handle->context, opnum, &arg, &heapref) != 0) { + status = NT_STATUS_UNSUCCESSFUL; + } else if (arg.status != 0) { + status = NT_SC_VALUE(arg.status); + } else { + + (void) memcpy(&samr_handle->handle, &arg.handle, + sizeof (ms_handle_t)); + + if (mlsvc_is_null_handle(samr_handle)) + status = NT_STATUS_INVALID_HANDLE; + } + + mlsvc_rpc_free(samr_handle->context, &heapref); + + return (status); +} + + +/* + * samr_close_handle + * + * This is function closes any valid handle, i.e. sam, domain, user etc. + * Just to be safe we check for, and reject, null handles. The handle + * returned by the SAM server is all null. If the handle being closed is + * the top level connect handle, we also close the pipe. Then we zero + * out the handle to invalidate it. Things go badly if you attempt to + * use an invalid handle, i.e. the DC crashes. + */ +int +samr_close_handle(mlsvc_handle_t *desc) +{ + struct samr_CloseHandle arg; + mlrpc_heapref_t heap; + int opnum; + int rc; + + if (mlsvc_is_null_handle(desc)) + return (-1); + + opnum = SAMR_OPNUM_CloseHandle; + bzero(&arg, sizeof (struct samr_CloseHandle)); + (void) memcpy(&arg.handle, &desc->handle, sizeof (ms_handle_t)); + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(desc->context, opnum, &arg, &heap); + mlsvc_rpc_free(desc->context, &heap); + + if (desc->context->handle == &desc->handle) { + (void) mlsvc_close_pipe(desc->context->fid); + free(desc->context); + } + + bzero(desc, sizeof (mlsvc_handle_t)); + return (rc); +} + +/* + * samr_open_domain + * + * We use a SAM handle to obtain a handle for a domain, specified by + * the SID. The SID can be obtain via the LSA interface. A handle for + * the domain is returned in domain_handle. + */ +DWORD +samr_open_domain(mlsvc_handle_t *samr_handle, DWORD access_mask, + struct samr_sid *sid, mlsvc_handle_t *domain_handle) +{ + struct samr_OpenDomain arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + DWORD status; + + if (mlsvc_is_null_handle(samr_handle) || + sid == 0 || domain_handle == 0) { + return (NT_STATUS_INVALID_PARAMETER); + } + + context = samr_handle->context; + opnum = SAMR_OPNUM_OpenDomain; + bzero(&arg, sizeof (struct samr_OpenDomain)); + (void) memcpy(&arg.handle, &samr_handle->handle, sizeof (ms_handle_t)); + + arg.access_mask = access_mask; + arg.sid = sid; + + (void) mlsvc_rpc_init(&heap); + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_UNSUCCESSFUL; + } else if (arg.status != 0) { + status = arg.status; + } else { + status = NT_STATUS_SUCCESS; + (void) memcpy(&domain_handle->handle, &arg.domain_handle, + sizeof (ms_handle_t)); + domain_handle->context = context; + if (mlsvc_is_null_handle(domain_handle)) + status = NT_STATUS_INVALID_HANDLE; + } + + if (status != NT_STATUS_SUCCESS) + mlsvc_rpc_report_status(opnum, status); + + mlsvc_rpc_free(context, &heap); + return (status); +} + +/* + * samr_open_user + * + * Use a domain handle to obtain a handle for a user, specified by the + * user RID. A user RID (effectively a uid) can be obtained via the + * LSA interface. A handle for the user is returned in user_handle. + * Once you have a user handle it should be possible to query the SAM + * for information on that user. + */ +int +samr_open_user(mlsvc_handle_t *domain_handle, DWORD access_mask, DWORD rid, + mlsvc_handle_t *user_handle) +{ + struct samr_OpenUser arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + + if (mlsvc_is_null_handle(domain_handle) || user_handle == NULL) + return (-1); + + context = domain_handle->context; + opnum = SAMR_OPNUM_OpenUser; + bzero(&arg, sizeof (struct samr_OpenUser)); + (void) memcpy(&arg.handle, &domain_handle->handle, + sizeof (ms_handle_t)); + arg.access_mask = access_mask; + arg.rid = rid; + + (void) mlsvc_rpc_init(&heap); + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + rc = -1; + } else { + (void) memcpy(&user_handle->handle, &arg.user_handle, + sizeof (ms_handle_t)); + user_handle->context = context; + + if (mlsvc_is_null_handle(user_handle)) + rc = -1; + } + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * samr_delete_user + * + * Delete the user specified by the user_handle. + */ +DWORD +samr_delete_user(mlsvc_handle_t *user_handle) +{ + struct samr_DeleteUser arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + DWORD status; + + if (mlsvc_is_null_handle(user_handle)) + return (NT_STATUS_INVALID_PARAMETER); + + context = user_handle->context; + opnum = SAMR_OPNUM_DeleteUser; + bzero(&arg, sizeof (struct samr_DeleteUser)); + (void) memcpy(&arg.user_handle, &user_handle->handle, + sizeof (ms_handle_t)); + + (void) mlsvc_rpc_init(&heap); + if (mlsvc_rpc_call(context, opnum, &arg, &heap) != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + status = NT_SC_VALUE(arg.status); + } else { + status = 0; + } + + mlsvc_rpc_free(context, &heap); + return (status); +} + +/* + * samr_open_group + * + * Use a domain handle to obtain a handle for a group, specified by the + * group RID. A group RID (effectively a gid) can be obtained via the + * LSA interface. A handle for the group is returned in group_handle. + * Once you have a group handle it should be possible to query the SAM + * for information on that group. + */ +int +samr_open_group( + mlsvc_handle_t *domain_handle, + DWORD rid, + mlsvc_handle_t *group_handle) +{ + struct samr_OpenGroup arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + + if (mlsvc_is_null_handle(domain_handle) || group_handle == 0) + return (-1); + + context = domain_handle->context; + opnum = SAMR_OPNUM_OpenGroup; + bzero(&arg, sizeof (struct samr_OpenUser)); + (void) memcpy(&arg.handle, &domain_handle->handle, + sizeof (ms_handle_t)); + arg.access_mask = SAM_LOOKUP_INFORMATION | SAM_ACCESS_USER_READ; + arg.rid = rid; + + (void) mlsvc_rpc_init(&heap); + + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc == 0) { + if (arg.status != 0) { + mlsvc_rpc_report_status(opnum, arg.status); + rc = -1; + } else { + (void) memcpy(&group_handle->handle, &arg.group_handle, + sizeof (ms_handle_t)); + group_handle->context = context; + if (mlsvc_is_null_handle(group_handle)) + rc = -1; + } + } + + mlsvc_rpc_free(context, &heap); + return (rc); +} + +/* + * samr_create_user + * + * Create a user in the domain specified by the domain handle. If this + * call is successful, the server will return the RID for the user and + * a user handle, which may be used to set or query the SAM. + * + * Observed status codes: + * NT_STATUS_INVALID_PARAMETER + * NT_STATUS_INVALID_ACCOUNT_NAME + * NT_STATUS_ACCESS_DENIED + * NT_STATUS_USER_EXISTS + * + * Returns 0 on success. Otherwise returns an NT status code. + */ +DWORD +samr_create_user(mlsvc_handle_t *domain_handle, char *username, + DWORD account_flags, DWORD *rid, mlsvc_handle_t *user_handle) +{ + struct samr_CreateUser arg; + struct mlsvc_rpc_context *context; + mlrpc_heapref_t heap; + int opnum; + int rc; + DWORD status = 0; + + if (mlsvc_is_null_handle(domain_handle) || + username == NULL || rid == NULL) { + return (NT_STATUS_INVALID_PARAMETER); + } + + context = domain_handle->context; + opnum = SAMR_OPNUM_CreateUser; + + bzero(&arg, sizeof (struct samr_CreateUser)); + (void) memcpy(&arg.handle, &domain_handle->handle, + sizeof (ms_handle_t)); + + (void) mlsvc_rpc_init(&heap); + mlrpc_heap_mkvcs(heap.heap, username, (mlrpc_vcbuf_t *)&arg.username); + + arg.account_flags = account_flags; + arg.unknown_e00500b0 = 0xE00500B0; + + rc = mlsvc_rpc_call(context, opnum, &arg, &heap); + if (rc != 0) { + status = NT_STATUS_INVALID_PARAMETER; + } else if (arg.status != 0) { + status = NT_SC_VALUE(arg.status); + + if (status != NT_STATUS_USER_EXISTS) { + smb_tracef("SamrCreateUser[%s]: %s", username, + xlate_nt_status(status)); + } + } else { + (void) memcpy(&user_handle->handle, &arg.user_handle, + sizeof (ms_handle_t)); + user_handle->context = context; + *rid = arg.rid; + + if (mlsvc_is_null_handle(user_handle)) + status = NT_STATUS_INVALID_HANDLE; + else + status = 0; + } + + mlsvc_rpc_free(context, &heap); + return (status); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/secdb.c b/usr/src/lib/smbsrv/libmlsvc/common/secdb.c new file mode 100644 index 0000000000..cc4d96456f --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/secdb.c @@ -0,0 +1,932 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Security database interface. + */ +#include <unistd.h> +#include <strings.h> +#include <pwd.h> +#include <grp.h> +#include <time.h> +#include <syslog.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libmlsvc.h> + +#include <smbsrv/smbinfo.h> +#include <smbsrv/smb_token.h> +#include <smbsrv/lsalib.h> +#include <smbsrv/alloc.h> + +extern uint32_t netlogon_logon(netr_client_t *clnt, smb_userinfo_t *uinfo); +static uint32_t smb_logon_domain(netr_client_t *clnt, smb_userinfo_t *uinfo); +static uint32_t smb_logon_local(netr_client_t *clnt, smb_userinfo_t *uinfo); +static uint32_t smb_logon_none(netr_client_t *clnt, smb_userinfo_t *uinfo); + +static uint32_t smb_setup_luinfo(smb_userinfo_t *, netr_client_t *, uid_t); + +static int smb_token_is_member(smb_token_t *token, nt_sid_t *sid); +static int smb_token_is_valid(smb_token_t *token); +static smb_win_grps_t *smb_token_create_wingrps(smb_userinfo_t *user_info); + +static smb_posix_grps_t *smb_token_create_pxgrps(uid_t uid); + +/* Consolidation private function from Network Repository */ +extern int _getgroupsbymember(const char *, gid_t[], int, int); + +static idmap_stat +smb_token_idmap(smb_token_t *token, smb_idmap_batch_t *sib) +{ + idmap_stat stat; + smb_idmap_t *sim; + smb_id_t *id; + int i; + + if (!token || !sib) + return (IDMAP_ERR_ARG); + + sim = sib->sib_maps; + + if (token->tkn_flags & SMB_ATF_ANON) { + token->tkn_user->i_id = UID_NOBODY; + token->tkn_owner->i_id = UID_NOBODY; + } else { + /* User SID */ + id = token->tkn_user; + sim->sim_id = &id->i_id; + stat = smb_idmap_batch_getid(sib->sib_idmaph, sim++, + id->i_sidattr.sid, SMB_IDMAP_USER); + + if (stat != IDMAP_SUCCESS) + return (stat); + + /* Owner SID */ + id = token->tkn_owner; + sim->sim_id = &id->i_id; + stat = smb_idmap_batch_getid(sib->sib_idmaph, sim++, + id->i_sidattr.sid, SMB_IDMAP_UNKNOWN); + + if (stat != IDMAP_SUCCESS) + return (stat); + } + + /* Primary Group SID */ + id = token->tkn_primary_grp; + sim->sim_id = &id->i_id; + stat = smb_idmap_batch_getid(sib->sib_idmaph, sim++, + id->i_sidattr.sid, SMB_IDMAP_GROUP); + + if (stat != IDMAP_SUCCESS) + return (stat); + + /* Other Windows Group SIDs */ + for (i = 0; i < token->tkn_win_grps->wg_count; i++, sim++) { + id = &token->tkn_win_grps->wg_groups[i]; + sim->sim_id = &id->i_id; + stat = smb_idmap_batch_getid(sib->sib_idmaph, sim, + id->i_sidattr.sid, SMB_IDMAP_GROUP); + + if (stat != IDMAP_SUCCESS) + break; + } + + return (stat); +} + +/* + * smb_token_sids2ids + * + * This will map all the SIDs of the access token to UIDs/GIDs. + * + * Returns 0 upon success. Otherwise, returns -1. + */ +static int +smb_token_sids2ids(smb_token_t *token) +{ + idmap_stat stat; + int nmaps, retries = 0; + smb_idmap_batch_t sib; + + /* + * Number of idmap lookups: user SID, owner SID, primary group SID, + * and all Windows group SIDs + */ + if (token->tkn_flags & SMB_ATF_ANON) + /* + * Don't include user and owner SID, they're Anonymous + */ + nmaps = 1; + else + nmaps = 3; + + nmaps += token->tkn_win_grps->wg_count; + + do { + stat = smb_idmap_batch_create(&sib, nmaps, SMB_IDMAP_SID2ID); + if (stat != IDMAP_SUCCESS) { + syslog(LOG_ERR, "smb_token_sids2ids:" + " idmap_get_create failed (%d)", stat); + return (-1); + } + + stat = smb_token_idmap(token, &sib); + if (stat != IDMAP_SUCCESS) { + smb_idmap_batch_destroy(&sib); + return (-1); + } + + stat = smb_idmap_batch_getmappings(&sib); + smb_idmap_batch_destroy(&sib); + if (stat == IDMAP_ERR_RPC_HANDLE) + if (smb_idmap_restart() < 0) + break; + } while (stat == IDMAP_ERR_RPC_HANDLE && retries++ < 3); + + return (stat == IDMAP_SUCCESS ? 0 : -1); +} + +/* + * smb_token_create_pxgrps + * + * Setup the POSIX group membership of the access token if the given UID is + * a POSIX UID (non-ephemeral). Both the user's primary group and + * supplementary groups will be added to the POSIX group array of the access + * token. + */ +static smb_posix_grps_t * +smb_token_create_pxgrps(uid_t uid) +{ + struct passwd *pwd; + smb_posix_grps_t *pgrps; + int ngroups_max, num; + gid_t *gids; + + if ((ngroups_max = sysconf(_SC_NGROUPS_MAX)) < 0) { + syslog(LOG_ERR, "smb_logon: failed to get _SC_NGROUPS_MAX"); + return (NULL); + } + + pwd = getpwuid(uid); + if (pwd == NULL) { + pgrps = malloc(sizeof (smb_posix_grps_t)); + if (pgrps == NULL) + return (NULL); + + pgrps->pg_ngrps = 0; + return (pgrps); + } + + if (pwd->pw_name == NULL) { + pgrps = malloc(sizeof (smb_posix_grps_t)); + if (pgrps == NULL) + return (NULL); + + pgrps->pg_ngrps = 1; + pgrps->pg_grps[0] = pwd->pw_gid; + return (pgrps); + } + + gids = (gid_t *)malloc(ngroups_max * sizeof (gid_t)); + if (gids == NULL) { + return (NULL); + } + bzero(gids, ngroups_max * sizeof (gid_t)); + + gids[0] = pwd->pw_gid; + + /* + * Setup the groups starting at index 1 (the last arg) + * of gids array. + */ + num = _getgroupsbymember(pwd->pw_name, gids, ngroups_max, 1); + + if (num == -1) { + syslog(LOG_ERR, "smb_logon: unable " + "to get user's supplementary groups"); + num = 1; + } + + pgrps = (smb_posix_grps_t *)malloc(SMB_POSIX_GRPS_SIZE(num)); + if (pgrps) { + pgrps->pg_ngrps = num; + bcopy(gids, pgrps->pg_grps, num * sizeof (gid_t)); + } + + free(gids); + return (pgrps); +} + +/* + * smb_token_destroy + * + * Release all of the memory associated with a token structure. Ensure + * that the token has been unlinked before calling. + */ +void +smb_token_destroy(smb_token_t *token) +{ + smb_win_grps_t *groups; + int i; + + if (token == NULL) + return; + + if (token->tkn_user) { + free(token->tkn_user->i_sidattr.sid); + free(token->tkn_user); + } + + if (token->tkn_owner) { + free(token->tkn_owner->i_sidattr.sid); + free(token->tkn_owner); + } + + if (token->tkn_primary_grp) { + free(token->tkn_primary_grp->i_sidattr.sid); + free(token->tkn_primary_grp); + } + + if ((groups = token->tkn_win_grps) != NULL) { + for (i = 0; i < groups->wg_count; ++i) + free(groups->wg_groups[i].i_sidattr.sid); + free(groups); + } + + smb_privset_free(token->tkn_privileges); + + free(token->tkn_posix_grps); + free(token->tkn_account_name); + free(token->tkn_domain_name); + free(token->tkn_session_key); + + free(token); +} + +static smb_id_t * +smb_token_create_id(nt_sid_t *sid) +{ + smb_id_t *id; + + id = (smb_id_t *)malloc(sizeof (smb_id_t)); + if (id == NULL) + return (NULL); + + id->i_id = (uid_t)-1; + id->i_sidattr.attrs = 7; + id->i_sidattr.sid = nt_sid_dup(sid); + + if (id->i_sidattr.sid == NULL) { + free(id); + id = NULL; + } + + return (id); +} + +/* + * Token owner should be set to local Administrators group + * in two cases: + * 1. The logged on user is a member of Domain Admins group + * 2. he/she is a member of local Administrators group + */ +static smb_id_t * +smb_token_create_owner(smb_userinfo_t *user_info) +{ + smb_id_t *owner; + +#ifdef PBSHORTCUT + if (user_info->flags & SMB_UINFO_FLAG_ADMIN) { + well_known_account_t *wka; + /* + * Need Winchester update on Group ID as file owner issue. + * For now, the file owner will always be set with user SID. + */ + wka = nt_builtin_lookup("Administratrors"); + owner = smb_token_create_id(wka->binsid); + } else +#endif + owner = smb_token_create_id(user_info->user_sid); + return (owner); +} + +static smb_privset_t * +smb_token_create_privs(smb_userinfo_t *user_info) +{ + smb_privset_t *privs; + nt_group_t *grp; + + privs = smb_privset_new(); + if (privs == NULL) + return (NULL); + + (void) nt_groups_member_privs(user_info->user_sid, privs); + + if (user_info->flags & SMB_UINFO_FLAG_ADMIN) { + grp = nt_group_getinfo("Administrators", RWLOCK_READER); + if (grp) { + nt_group_add_groupprivs(grp, privs); + nt_group_putinfo(grp); + } + + /* + * This privilege is required for view/edit of SACL + */ + smb_privset_enable(privs, SE_SECURITY_LUID); + } + + return (privs); +} + +static void +smb_token_set_flags(smb_token_t *token, smb_userinfo_t *user_info) +{ + well_known_account_t *wka; + + if (user_info->flags & SMB_UINFO_FLAG_ANON) { + token->tkn_flags |= SMB_ATF_ANON; + return; + } + + if (user_info->rid == DOMAIN_USER_RID_GUEST) { + token->tkn_flags |= SMB_ATF_GUEST; + return; + } + + wka = nt_builtin_lookup("Administrators"); + if (wka->binsid && smb_token_is_member(token, wka->binsid)) + token->tkn_flags |= SMB_ATF_ADMIN; + + wka = nt_builtin_lookup("Power Users"); + if (wka->binsid && smb_token_is_member(token, wka->binsid)) + token->tkn_flags |= SMB_ATF_POWERUSER; + + wka = nt_builtin_lookup("Backup Operators"); + if (wka->binsid && smb_token_is_member(token, wka->binsid)) + token->tkn_flags |= SMB_ATF_BACKUPOP; + +} + +/* + * smb_token_create + * + * Build an access token based on the given user information (user_info). + * + * If everything is successful, a pointer to an access token is + * returned. Otherwise a null pointer is returned. + */ +static smb_token_t * +smb_token_create(smb_userinfo_t *user_info) +{ + smb_token_t *token; + + if (user_info->sid_name_use != SidTypeUser) { + return (NULL); + } + + token = (smb_token_t *)malloc(sizeof (smb_token_t)); + if (token == NULL) { + syslog(LOG_ERR, "smb_token_create: resource shortage"); + return (NULL); + } + bzero(token, sizeof (smb_token_t)); + + /* User */ + token->tkn_user = smb_token_create_id(user_info->user_sid); + if (token->tkn_user == NULL) { + smb_token_destroy(token); + syslog(LOG_ERR, "smb_token_create: resource shortage"); + return (NULL); + } + + /* Owner */ + token->tkn_owner = smb_token_create_owner(user_info); + if (token->tkn_owner == NULL) { + syslog(LOG_ERR, "smb_token_create: resource shortage"); + smb_token_destroy(token); + return (NULL); + } + + /* Primary Group */ + token->tkn_primary_grp = smb_token_create_id(user_info->pgrp_sid); + if (token->tkn_primary_grp == NULL) { + syslog(LOG_ERR, "smb_token_create: resource shortage"); + smb_token_destroy(token); + return (NULL); + } + + /* Privileges */ + token->tkn_privileges = smb_token_create_privs(user_info); + if (token->tkn_privileges == NULL) { + smb_token_destroy(token); + syslog(LOG_ERR, "smb_token_create: resource shortage"); + return (NULL); + } + + /* Windows Groups */ + token->tkn_win_grps = smb_token_create_wingrps(user_info); + + smb_token_set_flags(token, user_info); + + /* + * IMPORTANT + * + * This function has to be called after all the SIDs in the + * token are setup (i.e. user, owner, primary and supplementary + * groups) and before setting up Solaris groups. + */ + if (smb_token_sids2ids(token) != 0) { + syslog(LOG_ERR, "smb_token_create: idmap failed"); + smb_token_destroy(token); + return (NULL); + } + + /* Solaris Groups */ + token->tkn_posix_grps = smb_token_create_pxgrps(token->tkn_user->i_id); + + if (user_info->session_key) { + token->tkn_session_key = malloc(sizeof (smb_session_key_t)); + if (token->tkn_session_key == NULL) { + syslog(LOG_ERR, "smb_token_create: resource shortage"); + smb_token_destroy(token); + return (NULL); + } + + (void) memcpy(token->tkn_session_key, + user_info->session_key, sizeof (smb_session_key_t)); + } + + token->tkn_account_name = strdup(user_info->name); + token->tkn_domain_name = strdup(user_info->domain_name); + + if (!smb_token_is_valid(token)) { + smb_token_destroy(token); + return (NULL); + } + + return (token); +} + +/* + * smb_token_create_wingrps + * + * This private function supports smb_token_create() by mapping the group + * information in the user_info structure to the form required in an + * access token. The main difference is that the user_info contains + * RIDs while and access token contains full SIDs. Memory allocated + * here will be deallocated as part of smb_token_destroy(). + * + * If everything is successful, a pointer to a smb_win_grps_t + * structure is returned. Otherwise a null pointer is returned. + */ +static smb_win_grps_t * +smb_token_create_wingrps(smb_userinfo_t *user_info) +{ + static char *wk_grps[] = + {"Authenticated Users", "NETWORK", "Administrators"}; + smb_win_grps_t *tkn_grps; + smb_sid_attrs_t *dlg_grps; + smb_rid_attrs_t *g_grps; + smb_sid_attrs_t *grp; + nt_sid_t *builtin_sid; + uint32_t n_gg, n_lg, n_dlg, n_wg; + uint32_t i, j; + int size, count; + + if (user_info == NULL) + return (NULL); + + n_gg = user_info->n_groups; /* Global Groups */ + n_dlg = user_info->n_other_grps; /* Domain Local Groups */ + + /* Local Groups */ + n_lg = nt_groups_member_ngroups(user_info->user_sid); + + /* Well known Groups */ + if ((user_info->flags & SMB_UINFO_FLAG_ADMIN) == SMB_UINFO_FLAG_DADMIN) + /* if user is a domain admin but not a local admin */ + n_wg = 3; + else if (user_info->flags & SMB_UINFO_FLAG_ANON) + n_wg = 0; + else + n_wg = 2; + + count = n_gg + n_dlg + n_lg + n_wg; + size = sizeof (smb_win_grps_t) + (count * sizeof (smb_id_t)); + + tkn_grps = (smb_win_grps_t *)malloc(size); + if (tkn_grps == NULL) { + return (NULL); + } + bzero(tkn_grps, size); + + tkn_grps->wg_count = count; + + /* Add global groups */ + g_grps = user_info->groups; + for (i = 0; i < n_gg; ++i) { + grp = &tkn_grps->wg_groups[i].i_sidattr; + grp->attrs = g_grps[i].attributes; + grp->sid = nt_sid_splice(user_info->domain_sid, g_grps[i].rid); + } + + if (n_gg == 0) { + /* + * if there's no global group should add the + * primary group. + */ + grp = &tkn_grps->wg_groups[i++].i_sidattr; + grp->attrs = 0x7; + grp->sid = nt_sid_dup(user_info->pgrp_sid); + tkn_grps->wg_count++; + } + + /* Add domain local groups */ + dlg_grps = user_info->other_grps; + for (j = 0; j < n_dlg; ++j, ++i) { + grp = &tkn_grps->wg_groups[i].i_sidattr; + grp->attrs = dlg_grps[j].attrs; + grp->sid = nt_sid_dup(dlg_grps[j].sid); + } + + if (n_lg) { + /* Add local groups */ + (void) nt_groups_member_groups(user_info->user_sid, + &tkn_grps->wg_groups[i], n_lg); + i += n_lg; + } + + /* Add well known groups */ + for (j = 0; j < n_wg; ++j, ++i) { + builtin_sid = nt_builtin_lookup_name(wk_grps[j], NULL); + tkn_grps->wg_groups[i].i_sidattr.sid = builtin_sid; + tkn_grps->wg_groups[i].i_sidattr.attrs = 0x7; + } + + return (tkn_grps); +} + +/* + * smb_logon + * + * Performs user authentication and creates a token if the + * authentication is successful. + * + * Returns pointer to the created token. + */ +smb_token_t * +smb_logon(netr_client_t *clnt) +{ + smb_token_t *token = NULL; + smb_userinfo_t *uinfo; + uint32_t status; + + if ((uinfo = mlsvc_alloc_user_info()) == 0) + return (NULL); + + switch (clnt->flags) { + case NETR_CFLG_DOMAIN: + /* Pass through authentication with DC */ + status = smb_logon_domain(clnt, uinfo); + break; + + case NETR_CFLG_LOCAL: + /* Local authentication */ + status = smb_logon_local(clnt, uinfo); + break; + + case NETR_CFLG_ANON: + /* Anonymous user; no authentication */ + status = smb_logon_none(clnt, uinfo); + break; + + default: + status = NT_STATUS_INVALID_PARAMETER; + break; + } + + if (status == NT_STATUS_SUCCESS) + token = smb_token_create(uinfo); + + mlsvc_free_user_info(uinfo); + return (token); +} + +/* + * smb_logon_domain + * + * Performs pass through authentication with PDC. + */ +static uint32_t +smb_logon_domain(netr_client_t *clnt, smb_userinfo_t *uinfo) +{ + uint32_t status; + + if ((status = netlogon_logon(clnt, uinfo)) != 0) { + if (status == NT_STATUS_CANT_ACCESS_DOMAIN_INFO) { + if ((status = netlogon_logon(clnt, uinfo)) != 0) { + syslog(LOG_INFO, "SmbLogon[%s\\%s]: %s", + clnt->domain, clnt->username, + xlate_nt_status(status)); + return (status); + } + } + } + + return (status); +} + +/* + * smb_logon_local + * + * Check to see if connected user has an entry in the local + * smbpasswd database. If it has, tries both LM hash and NT + * hash with user's password(s) to authenticate the user. + */ +static uint32_t +smb_logon_local(netr_client_t *clnt, smb_userinfo_t *uinfo) +{ + smb_passwd_t smbpw; + boolean_t lm_ok, nt_ok; + uint32_t status; + + if (smb_pwd_getpasswd(clnt->username, &smbpw) == NULL) { + /* + * If user doesn't have entry either in smbpasswd + * or passwd it's considered as an invalid user. + */ + status = NT_STATUS_NO_SUCH_USER; + syslog(LOG_NOTICE, "SmbLogon[%s\\%s]: %s", + clnt->domain, clnt->username, + xlate_nt_status(status)); + return (status); + } + + if (smbpw.pw_flags & SMB_PWF_DISABLE) + return (NT_STATUS_ACCOUNT_DISABLED); + + nt_ok = lm_ok = B_FALSE; + if ((smbpw.pw_flags & SMB_PWF_LM) && + (clnt->lm_password.lm_password_len != 0)) { + lm_ok = smb_auth_validate_lm( + clnt->challenge_key.challenge_key_val, + clnt->challenge_key.challenge_key_len, + &smbpw, + clnt->lm_password.lm_password_val, + clnt->lm_password.lm_password_len, + clnt->username); + } + + if (!lm_ok && (clnt->nt_password.nt_password_len != 0)) { + nt_ok = smb_auth_validate_nt( + clnt->challenge_key.challenge_key_val, + clnt->challenge_key.challenge_key_len, + &smbpw, + clnt->nt_password.nt_password_val, + clnt->nt_password.nt_password_len, + clnt->username); + } + + if (!nt_ok && !lm_ok) { + status = NT_STATUS_WRONG_PASSWORD; + syslog(LOG_NOTICE, "SmbLogon[%s\\%s]: %s", + clnt->domain, clnt->username, + xlate_nt_status(status)); + return (status); + } + + status = smb_setup_luinfo(uinfo, clnt, smbpw.pw_uid); + return (status); +} + +/* + * smb_logon_none + * + * Setup user information for anonymous user. + * No authentication is required. + */ +static uint32_t +smb_logon_none(netr_client_t *clnt, smb_userinfo_t *uinfo) +{ + return (smb_setup_luinfo(uinfo, clnt, (uid_t)-1)); +} + +/* + * smb_setup_luinfo + * + * Setup local user information based on the client information and + * user's record in the local password file. + */ +static uint32_t +smb_setup_luinfo(smb_userinfo_t *lui, netr_client_t *clnt, uid_t uid) +{ + idmap_stat stat; + smb_idmap_batch_t sib; + smb_idmap_t *umap, *gmap; + nt_group_t *grp; + struct passwd pw; + char pwbuf[1024]; + + lui->sid_name_use = SidTypeUser; + lui->domain_sid = nt_sid_dup(nt_domain_local_sid()); + lui->name = strdup(clnt->username); + lui->domain_name = strdup(clnt->domain); + lui->n_groups = 0; + lui->groups = NULL; + lui->n_other_grps = 0; + lui->other_grps = NULL; + lui->flags = 0; + + if (lui->name == NULL || lui->domain_name == NULL || + lui->domain_sid == NULL) + return (NT_STATUS_INVALID_PARAMETER); + + if (clnt->flags & NETR_CFLG_ANON) { + lui->user_sid = nt_builtin_lookup_name("Anonymous", NULL); + lui->pgrp_sid = nt_builtin_lookup_name("Anonymous", NULL); + lui->flags = SMB_UINFO_FLAG_ANON; + + if (lui->user_sid == NULL || lui->pgrp_sid == NULL) + return (NT_STATUS_NO_MEMORY); + + return (NT_STATUS_SUCCESS); + } + + if (getpwuid_r(uid, &pw, pwbuf, sizeof (pwbuf)) == NULL) + return (NT_STATUS_NO_SUCH_USER); + + /* Get the SID for user's uid & gid */ + stat = smb_idmap_batch_create(&sib, 2, SMB_IDMAP_ID2SID); + if (stat != IDMAP_SUCCESS) { + return (NT_STATUS_INTERNAL_ERROR); + } + + umap = &sib.sib_maps[0]; + stat = smb_idmap_batch_getsid(sib.sib_idmaph, umap, pw.pw_uid, + SMB_IDMAP_USER); + + if (stat != IDMAP_SUCCESS) { + smb_idmap_batch_destroy(&sib); + return (NT_STATUS_INTERNAL_ERROR); + } + + gmap = &sib.sib_maps[1]; + stat = smb_idmap_batch_getsid(sib.sib_idmaph, gmap, pw.pw_gid, + SMB_IDMAP_GROUP); + + if (stat != IDMAP_SUCCESS) { + smb_idmap_batch_destroy(&sib); + return (NT_STATUS_INTERNAL_ERROR); + } + + stat = smb_idmap_batch_getmappings(&sib); + + if (stat != IDMAP_SUCCESS) { + return (NT_STATUS_INTERNAL_ERROR); + } + + lui->rid = umap->sim_rid; + lui->user_sid = nt_sid_dup(umap->sim_sid); + + lui->primary_group_rid = gmap->sim_rid; + lui->pgrp_sid = nt_sid_dup(gmap->sim_sid); + + smb_idmap_batch_destroy(&sib); + + if ((lui->user_sid == NULL) || (lui->pgrp_sid == NULL)) + return (NT_STATUS_NO_MEMORY); + + grp = nt_group_getinfo("Administrators", RWLOCK_READER); + if (grp) { + if (nt_group_is_member(grp, lui->user_sid)) + lui->flags = SMB_UINFO_FLAG_LADMIN; + nt_group_putinfo(grp); + } + + return (NT_STATUS_SUCCESS); +} + +/* + * smb_token_is_valid + * + * check to see if specified fields of the given access + * token are valid. + * Returns 1 if all of them are valid; otherwise 0. + */ +static int +smb_token_is_valid(smb_token_t *token) +{ + int valid; + + valid = (token->tkn_user != 0) && + (token->tkn_user->i_sidattr.sid != 0) && + (token->tkn_privileges != 0) && + (token->tkn_win_grps != 0) && + (token->tkn_owner != 0) && + (token->tkn_owner->i_sidattr.sid != 0) && + (token->tkn_primary_grp != 0) && + (token->tkn_primary_grp->i_sidattr.sid != 0); + + return (valid); +} + +/* + * smb_token_user_sid + * + * Return a pointer to the user SID in the specified token. A null + * pointer indicates an error. + */ +static nt_sid_t * +smb_token_user_sid(smb_token_t *token) +{ + if (token && token->tkn_user) + return ((token)->tkn_user->i_sidattr.sid); + + return (NULL); +} + +/* + * smb_token_group_sid + * + * Return a pointer to the group SID as indicated by the iterator. + * Setting the iterator to 0 before calling this function will return + * the first group, which will always be the primary group. The + * iterator will be incremented before returning the SID so that this + * function can be used to cycle through the groups. The caller can + * adjust the iterator as required between calls to obtain any specific + * group. + * + * On success a pointer to the appropriate group SID will be returned. + * Otherwise a null pointer will be returned. + */ +static nt_sid_t * +smb_token_group_sid(smb_token_t *token, int *iterator) +{ + smb_win_grps_t *groups; + int index; + + if (token == NULL || iterator == NULL) { + return (NULL); + } + + if ((groups = token->tkn_win_grps) == NULL) { + return (NULL); + } + + index = *iterator; + + if (index < 0 || index >= groups->wg_count) { + return (NULL); + } + + ++(*iterator); + return (groups->wg_groups[index].i_sidattr.sid); +} + +/* + * smb_token_is_member + * + * This function will determine whether or not the specified SID is a + * member of a token. The user SID and all group SIDs are tested. + * Returns 1 if the SID is a member of the token. Otherwise returns 0. + */ +static int +smb_token_is_member(smb_token_t *token, nt_sid_t *sid) +{ + nt_sid_t *tsid; + int iterator = 0; + + tsid = smb_token_user_sid(token); + while (tsid) { + if (nt_sid_is_equal(tsid, sid)) + return (1); + + tsid = smb_token_group_sid(token, &iterator); + } + + return (0); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/smb_autohome.c b/usr/src/lib/smbsrv/libmlsvc/common/smb_autohome.c new file mode 100644 index 0000000000..a9810bd538 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/smb_autohome.c @@ -0,0 +1,413 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <pwd.h> +#include <sys/stat.h> +#include <smbsrv/libsmb.h> +#include <smbsrv/libmlsvc.h> +#include <smbsrv/smbinfo.h> + +#define SMB_AUTOHOME_KEYSIZ 128 +#define SMB_AUTOHOME_MAXARG 4 +#define SMB_AUTOHOME_BUFSIZ 2048 + +typedef struct smb_autohome_info { + struct smb_autohome_info *magic1; + FILE *fp; + smb_autohome_t autohome; + char buf[SMB_AUTOHOME_BUFSIZ]; + char *argv[SMB_AUTOHOME_MAXARG]; + int lineno; + struct smb_autohome_info *magic2; +} smb_autohome_info_t; + +static smb_autohome_info_t smb_ai; + +static smb_autohome_t *smb_autohome_make_entry(smb_autohome_info_t *); +static char *smb_autohome_keysub(const char *, char *, int); +static smb_autohome_info_t *smb_autohome_getinfo(void); + +/* + * Add an autohome share. See smb_autohome(4) for details. + * + * If share directory contains backslash path separators, they will + * be converted to forward slash to support NT/DOS path style for + * autohome shares. + * + * Returns 0 on success or -1 to indicate an error. + */ +int +smb_autohome_add(const char *username) +{ + lmshare_info_t si; + char *sharename; + smb_autohome_t *ai; + + if (username == NULL) + return (-1); + + if ((sharename = strdup(username)) == NULL) + return (-1); + + if ((ai = smb_autohome_lookup(sharename)) == NULL) { + free(sharename); + return (0); + } + + (void) memset(&si, 0, sizeof (lmshare_info_t)); + (void) strlcpy(si.directory, ai->ah_path, MAXPATHLEN); + (void) strsubst(si.directory, '\\', '/'); + (void) strlcpy(si.container, ai->ah_container, MAXPATHLEN); + + if (lmshare_is_dir(si.directory) == 0) { + free(sharename); + return (0); + } + + if (lmshare_exists(sharename) != 0) { + (void) lmshare_getinfo(sharename, &si); + if (!(si.mode & LMSHRM_TRANS)) { + free(sharename); + return (0); + } + } else { + (void) strcpy(si.share_name, sharename); + si.mode = LMSHRM_TRANS; + } + + (void) lmshare_add(&si, 0); + free(sharename); + return (0); +} + +/* + * Remove an autohome share. + * + * Returns 0 on success or -1 to indicate an error. + */ +int +smb_autohome_remove(const char *username) +{ + lmshare_info_t si; + char *sharename; + + if (username == NULL) + return (-1); + + if ((sharename = strdup(username)) == NULL) + return (-1); + + if (lmshare_getinfo(sharename, &si) == NERR_Success) { + if (si.mode & LMSHRM_TRANS) { + (void) lmshare_delete(sharename, 0); + } + } + + free(sharename); + return (0); +} + +/* + * Find out if a share is an autohome share. + * + * Returns 1 if the share is an autohome share. + * Otherwise returns 0. + */ +int +smb_is_autohome(const lmshare_info_t *si) +{ + if (si && (si->mode & LMSHRM_TRANS) && + (lmshare_is_restricted((char *)si->share_name) == 0)) { + return (1); + } + + return (0); +} + +/* + * Search the autohome database for the specified name. The name cannot + * be an empty string or begin with * or +. + * 1. Search the file for the specified name. + * 2. Check for the wildcard rule and, if present, treat it as a match. + * 3. Check for the nsswitch rule and, if present, lookup the name + * via the name services. Note that the nsswitch rule will never + * be applied if the wildcard rule is present. + * + * Returns a pointer to the entry on success or null on failure. + */ +smb_autohome_t * +smb_autohome_lookup(const char *name) +{ + struct passwd *pw; + smb_autohome_t *ah = 0; + + if (name == NULL) + return (NULL); + + if (*name == '\0' || *name == '*' || *name == '+') + return (NULL); + + smb_autohome_setent(); + + while ((ah = smb_autohome_getent(name)) != NULL) { + if (strcasecmp(ah->ah_name, name) == 0) + break; + } + + if (ah == NULL) { + smb_autohome_setent(); + + while ((ah = smb_autohome_getent(name)) != NULL) { + if (strcasecmp(ah->ah_name, "*") == 0) { + ah->ah_name = (char *)name; + break; + } + } + } + + if (ah == NULL) { + smb_autohome_setent(); + + while ((ah = smb_autohome_getent("+nsswitch")) != NULL) { + if (strcasecmp("+nsswitch", ah->ah_name) != 0) + continue; + if ((pw = getpwnam(name)) == NULL) { + ah = 0; + break; + } + + ah->ah_name = pw->pw_name; + + if (ah->ah_path) + ah->ah_container = ah->ah_path; + + ah->ah_path = pw->pw_dir; + break; + } + } + + smb_autohome_endent(); + return (ah); +} + +/* + * Open or rewind the autohome database. + */ +void +smb_autohome_setent(void) +{ + smb_autohome_info_t *si; + char *mappath; + char filename[MAXNAMELEN]; + + if ((si = smb_autohome_getinfo()) != 0) { + (void) fseek(si->fp, 0L, SEEK_SET); + si->lineno = 0; + return; + } + + if ((si = &smb_ai) == 0) + return; + + smb_config_rdlock(); + if ((mappath = smb_config_get(SMB_CI_AUTOHOME_MAP)) == NULL) + mappath = SMB_AUTOHOME_PATH; + (void) snprintf(filename, MAXNAMELEN, "%s/%s", mappath, + SMB_AUTOHOME_FILE); + smb_config_unlock(); + + if ((si->fp = fopen(filename, "r")) == NULL) + return; + + si->magic1 = si; + si->magic2 = si; + si->lineno = 0; +} + +/* + * Close the autohome database and invalidate the autohome info. + * We can't zero the whole info structure because the application + * should still have access to the data after the file is closed. + */ +void +smb_autohome_endent(void) +{ + smb_autohome_info_t *si; + + if ((si = smb_autohome_getinfo()) != 0) { + (void) fclose(si->fp); + si->fp = 0; + si->magic1 = 0; + si->magic2 = 0; + } +} + +/* + * Return the next entry in the autohome database, opening the file + * if necessary. Returns null on EOF or error. + * + * Note that we are not looking for the specified name. The name is + * only used for key substitution, so that the caller sees the entry + * in expanded form. + */ +smb_autohome_t * +smb_autohome_getent(const char *name) +{ + smb_autohome_info_t *si; + char *bp; + + if ((si = smb_autohome_getinfo()) == 0) { + smb_autohome_setent(); + + if ((si = smb_autohome_getinfo()) == 0) + return (0); + } + + /* + * Find the next non-comment, non-empty line. + * Anything after a # is a comment and can be discarded. + * Discard a newline to avoid it being included in the parsing + * that follows. + * Leading and training whitespace is discarded, and replicated + * whitespace is compressed to simplify the token parsing, + * although strsep() deals with that better than strtok(). + */ + do { + if (fgets(si->buf, SMB_AUTOHOME_BUFSIZ, si->fp) == 0) + return (0); + + ++si->lineno; + + if ((bp = strpbrk(si->buf, "#\r\n")) != 0) + *bp = '\0'; + + (void) trim_whitespace(si->buf); + bp = strcanon(si->buf, " \t"); + } while (*bp == '\0'); + + (void) smb_autohome_keysub(name, si->buf, SMB_AUTOHOME_BUFSIZ); + return (smb_autohome_make_entry(si)); +} + +/* + * Set up an autohome entry from the line buffer. The line should just + * contain tokens separated by single whitespace. The line format is: + * <username> <home-dir-path> <ADS container> + */ +static smb_autohome_t * +smb_autohome_make_entry(smb_autohome_info_t *si) +{ + char *bp; + int i; + + bp = si->buf; + + for (i = 0; i < SMB_AUTOHOME_MAXARG; ++i) + si->argv[i] = 0; + + for (i = 0; i < SMB_AUTOHOME_MAXARG; ++i) { + do { + if ((si->argv[i] = strsep((char **)&bp, " \t")) == 0) + break; + } while (*(si->argv[i]) == '\0'); + + if (si->argv[i] == 0) + break; + } + + if ((si->autohome.ah_name = si->argv[0]) == NULL) { + /* + * Sanity check: the name could be an empty + * string but it can't be a null pointer. + */ + return (0); + } + + if ((si->autohome.ah_path = si->argv[1]) == NULL) + si->autohome.ah_path = ""; + + if ((si->autohome.ah_container = si->argv[2]) == NULL) + si->autohome.ah_container = ""; + + return (&si->autohome); +} + +/* + * Substitute the ? and & map keys. + * ? is replaced by the first character of the name + * & is replaced by the whole name. + */ +static char * +smb_autohome_keysub(const char *name, char *buf, int buflen) +{ + char key[SMB_AUTOHOME_KEYSIZ]; + char *ampersand; + char *tmp; + + (void) strlcpy(key, buf, SMB_AUTOHOME_KEYSIZ); + + if ((tmp = strpbrk(key, " \t")) == NULL) + return (NULL); + + *tmp = '\0'; + + if (strcmp(key, "*") == 0 && name != NULL) + (void) strlcpy(key, name, SMB_AUTOHOME_KEYSIZ); + + (void) strsubst(buf, '?', *key); + + while ((ampersand = strchr(buf, '&')) != NULL) { + if ((tmp = strdup(ampersand + 1)) == NULL) + return (0); + + (void) strlcpy(ampersand, key, buflen); + (void) strlcat(ampersand, tmp, buflen); + free(tmp); + } + + return (buf); +} + +/* + * Get a pointer to the context buffer and validate it. + */ +static smb_autohome_info_t * +smb_autohome_getinfo(void) +{ + smb_autohome_info_t *si; + + if ((si = &smb_ai) == 0) + return (0); + + if ((si->magic1 == si) && (si->magic2 == si) && (si->fp != NULL)) + return (si); + + return (0); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/smb_share_util.c b/usr/src/lib/smbsrv/libmlsvc/common/smb_share_util.c new file mode 100644 index 0000000000..8e5a7ca031 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/smb_share_util.c @@ -0,0 +1,106 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <pthread.h> +#include <synch.h> +#include <strings.h> +#include <stdlib.h> +#include <libshare.h> +#include <smbsrv/lmshare.h> + +static pthread_mutex_t smb_group_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* Sharemanager shared API */ + +void +smb_build_lmshare_info(char *share_name, char *path, + sa_optionset_t opts, lmshare_info_t *si) +{ + sa_property_t prop; + char *val = NULL; + + bzero(si, sizeof (lmshare_info_t)); + /* Share is read from SMF so it should be permanent */ + si->mode = LMSHRM_PERM; + + (void) strlcpy(si->directory, path, sizeof (si->directory)); + (void) strlcpy(si->share_name, share_name, sizeof (si->share_name)); + + if (opts == NULL) + return; + + prop = (sa_property_t)sa_get_property(opts, SHOPT_AD_CONTAINER); + if (prop != NULL) { + if ((val = sa_get_property_attr(prop, "value")) != NULL) { + (void) strlcpy(si->container, val, + sizeof (si->container)); + free(val); + } + } + + prop = (sa_property_t)sa_get_property(opts, "description"); + if (prop != NULL) { + if ((val = sa_get_property_attr(prop, "value")) != NULL) { + (void) strlcpy(si->comment, val, sizeof (si->comment)); + free(val); + } + } +} + +/* + * smb_get_smb_share_group + * + * Creates "smb" share group for putting in shares + * created by windows client. + */ +sa_group_t +smb_get_smb_share_group(sa_handle_t handle) +{ + sa_group_t group = NULL; + int err; + + (void) pthread_mutex_lock(&smb_group_mutex); + group = sa_get_group(handle, SMB_DEFAULT_SHARE_GROUP); + if (group != NULL) { + (void) pthread_mutex_unlock(&smb_group_mutex); + return (group); + } + group = sa_create_group(handle, SMB_DEFAULT_SHARE_GROUP, &err); + if (group == NULL) { + (void) pthread_mutex_unlock(&smb_group_mutex); + return (NULL); + } + if (group != NULL) { + if (sa_create_optionset(group, + SMB_DEFAULT_SHARE_GROUP) == NULL) { + (void) sa_remove_group(group); + group = NULL; + } + } + (void) pthread_mutex_unlock(&smb_group_mutex); + return (group); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/common/srvsvc_client.c b/usr/src/lib/smbsrv/libmlsvc/common/srvsvc_client.c new file mode 100644 index 0000000000..aa9b12d241 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/common/srvsvc_client.c @@ -0,0 +1,554 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Server Service (srvsvc) client side RPC library interface. The + * srvsvc interface allows a client to query a server for information + * on shares, sessions, connections and files on the server. Some + * functions are available via anonymous IPC while others require + * administrator privilege. Also, some functions return NT status + * values while others return Win32 errors codes. + */ + +#include <sys/errno.h> +#include <stdio.h> +#include <time.h> +#include <strings.h> +#include <time.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/libsmbrdr.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/ndl/srvsvc.ndl> +#include <smbsrv/mlsvc_util.h> + +/* + * Information level for NetShareGetInfo. + */ +DWORD srvsvc_info_level = 1; + +static int srvsvc_net_remote_tod(char *, char *, struct timeval *, struct tm *); + +/* + * Ensure that an appropriate session and logon exists for the srvsvc + * client calls. Open and bind the RPC interface. + * + * On success 0 is returned. Otherwise a -ve error code. + */ +int +srvsvc_open(int ipc_mode, char *server, char *domain, char *username, + char *password, mlsvc_handle_t *handle, mlrpc_heapref_t *heapref) +{ + smb_ntdomain_t *di; + int fid; + int rc; + + if ((di = smb_getdomaininfo(0)) == NULL) + return (-1); + + if (server == NULL || domain == NULL) { + server = di->server; + domain = di->domain; + } + + switch (ipc_mode) { + case MLSVC_IPC_USER: + /* + * Use the supplied credentials. + */ + rc = mlsvc_user_logon(server, domain, username, password); + break; + + case MLSVC_IPC_ADMIN: + /* + * Use the resource domain administrator credentials. + */ + server = di->server; + domain = di->domain; + username = smbrdr_ipc_get_user(); + + rc = mlsvc_admin_logon(server, domain); + break; + + case MLSVC_IPC_ANON: + default: + rc = mlsvc_anonymous_logon(server, domain, &username); + break; + } + + if (rc != 0) + return (-1); + + fid = mlsvc_open_pipe(server, domain, username, "\\srvsvc"); + if (fid < 0) + return (-1); + + if ((rc = mlsvc_rpc_bind(handle, fid, "SRVSVC")) < 0) { + (void) mlsvc_close_pipe(fid); + return (rc); + } + + rc = mlsvc_rpc_init(heapref); + return (rc); +} + +/* + * Close the srvsvc pipe and free the associated context. This function + * should only be called if the open was successful. + */ +void +srvsvc_close(mlsvc_handle_t *handle, mlrpc_heapref_t *heapref) +{ + mlsvc_rpc_free(handle->context, heapref); + (void) mlsvc_close_pipe(handle->context->fid); + free(handle->context); +} + +/* + * This is a client side routine for NetShareGetInfo. + * Levels 0 and 1 work with an anonymous connection but + * level 2 requires administrator access. + */ +int +srvsvc_net_share_get_info(char *server, char *domain, char *netname) +{ + struct mlsm_NetShareGetInfo arg; + mlsvc_handle_t handle; + mlrpc_heapref_t heap; + int rc; + int opnum; + struct mslm_NetShareGetInfo0 *info0; + struct mslm_NetShareGetInfo1 *info1; + struct mslm_NetShareGetInfo2 *info2; + int ipc_mode; + int len; + + if (netname == NULL) + return (-1); + + if (srvsvc_info_level == 2) + ipc_mode = MLSVC_IPC_ADMIN; + else + ipc_mode = MLSVC_IPC_ANON; + + rc = srvsvc_open(ipc_mode, server, domain, 0, 0, &handle, &heap); + if (rc != 0) + return (-1); + + opnum = SRVSVC_OPNUM_NetShareGetInfo; + bzero(&arg, sizeof (struct mlsm_NetShareGetInfo)); + + len = strlen(server) + 4; + arg.servername = mlrpc_heap_malloc(heap.heap, len); + if (arg.servername == NULL) { + srvsvc_close(&handle, &heap); + return (-1); + } + + (void) snprintf((char *)arg.servername, len, "\\\\%s", server); + arg.netname = (LPTSTR)netname; + arg.level = srvsvc_info_level; /* share information level */ + + rc = mlsvc_rpc_call(handle.context, opnum, &arg, &heap); + if ((rc != 0) || (arg.status != 0)) { + srvsvc_close(&handle, &heap); + return (-1); + } + + switch (arg.result.switch_value) { + case 0: + info0 = arg.result.ru.info0; + smb_tracef("srvsvc shi0_netname=%s", info0->shi0_netname); + break; + + case 1: + info1 = arg.result.ru.info1; + smb_tracef("srvsvc shi1_netname=%s", info1->shi1_netname); + smb_tracef("srvsvc shi1_type=%u", info1->shi1_type); + + if (info1->shi1_comment) + smb_tracef("srvsvc shi1_comment=%s", + info1->shi1_comment); + break; + + case 2: + info2 = arg.result.ru.info2; + smb_tracef("srvsvc shi2_netname=%s", info2->shi2_netname); + smb_tracef("srvsvc shi2_type=%u", info2->shi2_type); + + if (info2->shi2_comment) + smb_tracef("srvsvc shi2_comment=%s", + info2->shi2_comment); + + smb_tracef("srvsvc shi2_perms=%d", info2->shi2_permissions); + smb_tracef("srvsvc shi2_max_use=%d", info2->shi2_max_uses); + smb_tracef("srvsvc shi2_cur_use=%d", info2->shi2_current_uses); + + if (info2->shi2_path) + smb_tracef("srvsvc shi2_path=%s", info2->shi2_path); + + if (info2->shi2_passwd) + smb_tracef("srvsvc shi2_passwd=%s", info2->shi2_passwd); + break; + + default: + smb_tracef("srvsvc: unknown level"); + break; + } + + srvsvc_close(&handle, &heap); + return (0); +} + +/* + * This is a client side routine for NetSessionEnum. + * NetSessionEnum requires administrator rights. + */ +int +srvsvc_net_session_enum(char *server, char *domain, char *netname) +{ + struct mslm_NetSessionEnum arg; + mlsvc_handle_t handle; + mlrpc_heapref_t heap; + int rc; + int opnum; + struct mslm_infonres infonres; + struct mslm_SESSION_INFO_1 *nsi1; + int len; + + if (netname == NULL) + return (-1); + + rc = srvsvc_open(MLSVC_IPC_ADMIN, server, domain, 0, 0, &handle, &heap); + if (rc != 0) + return (-1); + + opnum = SRVSVC_OPNUM_NetSessionEnum; + bzero(&arg, sizeof (struct mslm_NetSessionEnum)); + + len = strlen(server) + 4; + arg.servername = mlrpc_heap_malloc(heap.heap, len); + if (arg.servername == NULL) { + srvsvc_close(&handle, &heap); + return (-1); + } + + (void) snprintf((char *)arg.servername, len, "\\\\%s", server); + infonres.entriesread = 0; + infonres.entries = 0; + arg.level = 1; + arg.result.level = 1; + arg.result.bufptr.p = &infonres; + arg.resume_handle = 0; + arg.pref_max_len = 0xFFFFFFFF; + + rc = mlsvc_rpc_call(handle.context, opnum, &arg, &heap); + if ((rc != 0) || (arg.status != 0)) { + srvsvc_close(&handle, &heap); + return (-1); + } + + /* Only the first session info is dereferenced. */ + nsi1 = ((struct mslm_infonres *)arg.result.bufptr.p)->entries; + + smb_tracef("srvsvc switch_value=%d", arg.level); + smb_tracef("srvsvc sesi1_cname=%s", nsi1->sesi1_cname); + smb_tracef("srvsvc sesi1_uname=%s", nsi1->sesi1_uname); + smb_tracef("srvsvc sesi1_nopens=%u", nsi1->sesi1_nopens); + smb_tracef("srvsvc sesi1_time=%u", nsi1->sesi1_time); + smb_tracef("srvsvc sesi1_itime=%u", nsi1->sesi1_itime); + smb_tracef("srvsvc sesi1_uflags=%u", nsi1->sesi1_uflags); + + srvsvc_close(&handle, &heap); + return (0); +} + +/* + * This is a client side routine for NetConnectEnum. + * NetConnectEnum requires administrator rights. + * Level 0 and level 1 requests are supported. + */ +int +srvsvc_net_connect_enum(char *server, char *domain, char *netname, int level) +{ + struct mslm_NetConnectEnum arg; + mlsvc_handle_t handle; + mlrpc_heapref_t heap; + int rc; + int opnum; + struct mslm_NetConnectInfo1 info1; + struct mslm_NetConnectInfo0 info0; + struct mslm_NetConnectInfoBuf1 *cib1; + int len; + + if (netname == NULL) + return (-1); + + rc = srvsvc_open(MLSVC_IPC_ADMIN, server, domain, 0, 0, &handle, &heap); + if (rc != 0) + return (-1); + + opnum = SRVSVC_OPNUM_NetConnectEnum; + bzero(&arg, sizeof (struct mslm_NetConnectEnum)); + + len = strlen(server) + 4; + arg.servername = mlrpc_heap_malloc(heap.heap, len); + if (arg.servername == NULL) { + srvsvc_close(&handle, &heap); + return (-1); + } + + (void) snprintf((char *)arg.servername, len, "\\\\%s", server); + arg.qualifier = (LPTSTR)netname; + + switch (level) { + case 0: + arg.info.level = 0; + arg.info.switch_value = 0; + arg.info.ru.info0 = &info0; + info0.entries_read = 0; + info0.ci0 = 0; + break; + case 1: + arg.info.level = 1; + arg.info.switch_value = 1; + arg.info.ru.info1 = &info1; + info1.entries_read = 0; + info1.ci1 = 0; + break; + default: + srvsvc_close(&handle, &heap); + return (-1); + } + + arg.resume_handle = 0; + arg.pref_max_len = 0xFFFFFFFF; + + rc = mlsvc_rpc_call(handle.context, opnum, &arg, &heap); + if ((rc != 0) || (arg.status != 0)) { + srvsvc_close(&handle, &heap); + return (-1); + } + + smb_tracef("srvsvc switch_value=%d", arg.info.switch_value); + + switch (level) { + case 0: + if (arg.info.ru.info0 && arg.info.ru.info0->ci0) { + smb_tracef("srvsvc coni0_id=%x", + arg.info.ru.info0->ci0->coni0_id); + } + break; + case 1: + if (arg.info.ru.info1 && arg.info.ru.info1->ci1) { + cib1 = arg.info.ru.info1->ci1; + + smb_tracef("srvsvc coni_uname=%s", + cib1->coni1_username ? + (char *)cib1->coni1_username : "(null)"); + smb_tracef("srvsvc coni1_netname=%s", + cib1->coni1_netname ? + (char *)cib1->coni1_netname : "(null)"); + smb_tracef("srvsvc coni1_nopens=%u", + cib1->coni1_num_opens); + smb_tracef("srvsvc coni1_time=%u", cib1->coni1_time); + smb_tracef("srvsvc coni1_num_users=%u", + cib1->coni1_num_users); + } + break; + + default: + smb_tracef("srvsvc: unknown level"); + break; + } + + srvsvc_close(&handle, &heap); + return (0); +} + +/* + * Synchronize the local system clock with the domain controller. + */ +void +srvsvc_timesync(void) +{ + smb_ntdomain_t *di; + struct timeval tv; + struct tm tm; + time_t tsecs; + + if ((di = smb_getdomaininfo(0)) == NULL) + return; + + if (srvsvc_net_remote_tod(di->server, di->domain, &tv, &tm) != 0) + return; + + if (settimeofday(&tv, 0)) + smb_tracef("unable to set system time"); + + tsecs = time(0); + (void) localtime_r(&tsecs, &tm); + smb_tracef("SrvsvcTimeSync %s", ctime((time_t *)&tv.tv_sec)); +} + +/* + * NetRemoteTOD to get the current GMT time from a Windows NT server. + */ +int +srvsvc_gettime(unsigned long *t) +{ + smb_ntdomain_t *di; + struct timeval tv; + struct tm tm; + + if ((di = smb_getdomaininfo(0)) == NULL) + return (-1); + + if (srvsvc_net_remote_tod(di->server, di->domain, &tv, &tm) != 0) + return (-1); + + *t = tv.tv_sec; + return (0); +} + +/* + * This is a client side routine for NetRemoteTOD, which gets the time + * and date from a remote system. The time information is returned in + * the timeval and tm. + * + * typedef struct _TIME_OF_DAY_INFO { + * DWORD tod_elapsedt; // seconds since 00:00:00 January 1 1970 GMT + * DWORD tod_msecs; // arbitrary milliseconds (since reset) + * DWORD tod_hours; // current hour [0-23] + * DWORD tod_mins; // current minute [0-59] + * DWORD tod_secs; // current second [0-59] + * DWORD tod_hunds; // current hundredth (0.01) second [0-99] + * LONG tod_timezone; // time zone of the server + * DWORD tod_tinterval; // clock tick time interval + * DWORD tod_day; // day of the month [1-31] + * DWORD tod_month; // month of the year [1-12] + * DWORD tod_year; // current year + * DWORD tod_weekday; // day of the week since sunday [0-6] + * } TIME_OF_DAY_INFO; + * + * The time zone of the server is calculated in minutes from Greenwich + * Mean Time (GMT). For time zones west of Greenwich, the value is + * positive; for time zones east of Greenwich, the value is negative. + * A value of -1 indicates that the time zone is undefined. + * + * The clock tick value represents a resolution of one ten-thousandth + * (0.0001) second. + */ +int +srvsvc_net_remote_tod(char *server, char *domain, struct timeval *tv, + struct tm *tm) +{ + char timebuf[64]; + struct mslm_NetRemoteTOD arg; + struct mslm_TIME_OF_DAY_INFO *tod; + mlsvc_handle_t handle; + mlrpc_heapref_t heap; + int rc; + int opnum; + int len; + + rc = srvsvc_open(MLSVC_IPC_ANON, server, domain, 0, 0, &handle, &heap); + if (rc != 0) + return (-1); + + opnum = SRVSVC_OPNUM_NetRemoteTOD; + bzero(&arg, sizeof (struct mslm_NetRemoteTOD)); + + len = strlen(server) + 4; + arg.servername = mlrpc_heap_malloc(heap.heap, len); + if (arg.servername == NULL) { + srvsvc_close(&handle, &heap); + return (-1); + } + + (void) snprintf((char *)arg.servername, len, "\\\\%s", server); + + rc = mlsvc_rpc_call(handle.context, opnum, &arg, &heap); + if ((rc != 0) || (arg.status != 0)) { + srvsvc_close(&handle, &heap); + return (-1); + } + + /* + * We're assigning milliseconds to microseconds + * here but the value's not really relevant. + */ + tod = arg.bufptr; + + if (tv) { + tv->tv_sec = tod->tod_elapsedt; + tv->tv_usec = tod->tod_msecs; + smb_tracef("RemoteTime: %s", ctime(&tv->tv_sec)); + } + + if (tm) { + tm->tm_sec = tod->tod_secs; + tm->tm_min = tod->tod_mins; + tm->tm_hour = tod->tod_hours; + tm->tm_mday = tod->tod_day; + tm->tm_mon = tod->tod_month - 1; + tm->tm_year = tod->tod_year - 1900; + tm->tm_wday = tod->tod_weekday; + + (void) strftime(timebuf, sizeof (timebuf), + "NetRemoteTOD: %D %T", tm); + smb_tracef("NetRemoteTOD: %s", timebuf); + } + + srvsvc_close(&handle, &heap); + return (0); +} + +void +srvsvc_net_test(char *server, char *domain, char *netname) +{ + smb_ntdomain_t *di; + + (void) smb_tracef("%s %s %s", server, domain, netname); + + if ((di = smb_getdomaininfo(0)) != NULL) { + server = di->server; + domain = di->domain; + } + + (void) srvsvc_net_share_get_info(server, domain, netname); +#if 0 + /* + * The NetSessionEnum server-side definition was updated. + * Disabled until the client-side has been updated. + */ + (void) srvsvc_net_session_enum(server, domain, netname); +#endif + (void) srvsvc_net_connect_enum(server, domain, netname, 0); + (void) srvsvc_net_connect_enum(server, domain, netname, 1); +} diff --git a/usr/src/lib/smbsrv/libmlsvc/i386/Makefile b/usr/src/lib/smbsrv/libmlsvc/i386/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/i386/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libmlsvc/sparc/Makefile b/usr/src/lib/smbsrv/libmlsvc/sparc/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/sparc/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libmlsvc/sparcv9/Makefile b/usr/src/lib/smbsrv/libmlsvc/sparcv9/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlsvc/sparcv9/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/smbsrv/libsmb/Makefile b/usr/src/lib/smbsrv/libsmb/Makefile new file mode 100644 index 0000000000..cbe44c764c --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +HDRS= libsmb.h + +include ../Makefile.smbsrv diff --git a/usr/src/lib/smbsrv/libsmb/Makefile.com b/usr/src/lib/smbsrv/libsmb/Makefile.com new file mode 100644 index 0000000000..c479406282 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/Makefile.com @@ -0,0 +1,86 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +LIBRARY= libsmb.a +VERS= .1 + +OBJS_SHARED = \ + smb_common_door_decode.o \ + smb_match.o \ + smb_msgbuf.o \ + smb_native.o \ + smb_oem.o \ + smb_opmlang.o \ + smb_share_door_decode.o \ + smb_sid.o \ + smb_status_xlat.o \ + smb_strcase.o \ + smb_string.o \ + smb_token.o \ + smb_token_xdr.o \ + smb_utf8.o \ + smb_xdr_utils.o + +OBJS_COMMON = \ + smb_api_door_calls.o \ + smb_auth.o \ + smb_cfg.o \ + smb_crypt.o \ + smb_ctxbuf.o \ + smb_domain.o \ + smb_door_client.o \ + smb_door_encdec.o \ + smb_doorclnt.o \ + smb_downcalls.o \ + smb_group_door_encdec.o \ + smb_group_xdr.o \ + smb_ht.o \ + smb_idmap.o \ + smb_info.o \ + smb_mac.o \ + smb_pwdutil.o \ + smb_privilege.o \ + smb_scfutil.o \ + smb_util.o \ + smb_wins.o \ + smb_wksids.o + +OBJECTS= $(OBJS_COMMON) $(OBJS_SHARED) + +include ../../../Makefile.lib +include ../../Makefile.lib + +INCS += -I$(SRC)/common/smbsrv + +LDLIBS += -lscf -lmd -lnsl -lpkcs11 -lc -lidmap +CPPFLAGS += $(INCS) -D_REENTRANT + +SRCS= $(OBJS_COMMON:%.o=$(SRCDIR)/%.c) \ + $(OBJS_SHARED:%.o=$(SRC)/common/smbsrv/%.c) + +include ../../Makefile.targ +include ../../../Makefile.targ diff --git a/usr/src/lib/smbsrv/libsmb/amd64/Makefile b/usr/src/lib/smbsrv/libsmb/amd64/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/amd64/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/smbsrv/libsmb/common/libsmb.h b/usr/src/lib/smbsrv/libsmb/common/libsmb.h new file mode 100644 index 0000000000..44da30085d --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/libsmb.h @@ -0,0 +1,778 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBSMB_H +#define _LIBSMB_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> +#include <arpa/inet.h> + +#include <stdlib.h> +#include <libscf.h> +#include <libshare.h> + +#include <smbsrv/smb_idmap.h> + +/* + * XXX - These header files are here, only because other libraries + * can compile. Move the header files in to the internal header files + * of other libraries, once the restructure is complete. libsmb.h does not + * need these header files. + */ +#include <smbsrv/lmshare.h> +#include <smbsrv/lmshare_door.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/smb_door_svc.h> +#include <smbsrv/alloc.h> +#include <smbsrv/codepage.h> +#include <smbsrv/crypt.h> +#include <smbsrv/ctype.h> +#include <smbsrv/hash_table.h> +#include <smbsrv/msgbuf.h> +#include <smbsrv/oem.h> +#include <smbsrv/string.h> +#include <smbsrv/smb_i18n.h> +#include <smbsrv/wintypes.h> +#include <smbsrv/smb_xdr.h> +#include <smbsrv/smbinfo.h> +/* End of header files to be removed. */ + +/* Max value length of all SMB properties */ +#define MAX_VALUE_BUFLEN 512 +#define SMB_PI_MAX_DOMAIN_U 48 + +#define SMBD_FMRI_PREFIX "network/smb/server" +#define SMBD_DEFAULT_INSTANCE_FMRI "svc:/network/smb/server:default" +#define SMBD_PG_NAME "smbd" +#define SMBD_PROTECTED_PG_NAME "read" + +#define SMBD_SMF_OK 0 +#define SMBD_SMF_NO_MEMORY 1 /* no memory for data structures */ +#define SMBD_SMF_SYSTEM_ERR 2 /* system error, use errno */ +#define SMBD_SMF_NO_PERMISSION 3 /* no permission for operation */ + +#define SCH_STATE_UNINIT 0 +#define SCH_STATE_INITIALIZING 1 +#define SCH_STATE_INIT 2 + +typedef struct smb_scfhandle { + scf_handle_t *scf_handle; + int scf_state; + scf_service_t *scf_service; + scf_scope_t *scf_scope; + scf_transaction_t *scf_trans; + scf_transaction_entry_t *scf_entry; + scf_propertygroup_t *scf_pg; + scf_instance_t *scf_instance; + scf_iter_t *scf_inst_iter; + scf_iter_t *scf_pg_iter; +} smb_scfhandle_t; + +/* + * CIFS Configuration Management + */ + +/* macros for the description of all config params */ +#define SMB_CD_RDR_IPCMODE "rdr_ipcmode" +#define SMB_CD_RDR_IPCUSER "rdr_ipcuser" +#define SMB_CD_RDR_IPCPWD "rdr_ipcpasswd" + +#define SMB_CD_OPLOCK_ENABLE "oplock_enable" +#define SMB_CD_OPLOCK_TIMEOUT "oplock_timeout" + +#define SMB_CD_AUTOHOME_MAP "autohome_map" + +#define SMB_CD_DOMAIN_SID "domain_sid" +#define SMB_CD_DOMAIN_MEMB "domain_member" +#define SMB_CD_DOMAIN_NAME "domain_name" +#define SMB_CD_DOMAIN_SRV "pdc" + +#define SMB_CD_WINS_SRV1 "wins_server_1" +#define SMB_CD_WINS_SRV2 "wins_server_2" +#define SMB_CD_WINS_EXCL "wins_exclude" + +#define SMB_CD_SRVSVC_SHRSET_ENABLE "srvsvc_sharesetinfo_enable" +#define SMB_CD_LOGR_ENABLE "logr_enable" +#define SMB_CD_MLRPC_KALIVE "mlrpc_keep_alive_interval" + +#define SMB_CD_MAX_BUFSIZE "max_bufsize" +#define SMB_CD_MAX_WORKERS "max_workers" +#define SMB_CD_MAX_CONNECTIONS "max_connections" +#define SMB_CD_KEEPALIVE "keep_alive" +#define SMB_CD_RESTRICT_ANON "restrict_anonymous" + +#define SMB_CD_SIGNING_ENABLE "signing_enabled" +#define SMB_CD_SIGNING_REQD "signing_required" +#define SMB_CD_SIGNING_CHECK "signing_check" + +#define SMB_CD_FLUSH_REQUIRED "flush_required" +#define SMB_CD_SYNC_ENABLE "sync_enable" +#define SMB_CD_DIRSYMLINK_DISABLE "dir_symlink_disable" +#define SMB_CD_ANNONCE_QUOTA "announce_quota" + +#define SMB_CD_SECURITY "security" +#define SMB_CD_NBSCOPE "netbios_scope" +#define SMB_CD_SYS_CMNT "system_comment" +#define SMB_CD_LM_LEVEL "lmauth_level" +#define SMB_CD_MSDCS_DISABLE "msdcs_disable" + +#define SMB_CD_ADS_ENABLE "ads_enable" +#define SMB_CD_ADS_USER "ads_user" +#define SMB_CD_ADS_PASSWD "ads_passwd" +#define SMB_CD_ADS_DOMAIN "ads_domain" +#define SMB_CD_ADS_USER_CONTAINER "ads_user_container" +#define SMB_CD_ADS_SITE "ads_site" +#define SMB_CD_ADS_IPLOOKUP "ads_ip_lookup" + +#define SMB_CD_DYNDNS_ENABLE "ddns_enable" +#define SMB_CD_DYNDNS_RETRY_COUNT "ddns_retry_cnt" +#define SMB_CD_DYNDNS_RETRY_SEC "ddns_retry_sec" + +#define SMB_CD_MACHINE_PASSWD "machine_passwd" + +/* configuration identifier */ +typedef enum { + SMB_CI_RDR_IPCMODE = 0, + SMB_CI_RDR_IPCUSER, + SMB_CI_RDR_IPCPWD, + + SMB_CI_OPLOCK_ENABLE, + SMB_CI_OPLOCK_TIMEOUT, + + SMB_CI_AUTOHOME_MAP, + + SMB_CI_DOMAIN_SID, + SMB_CI_DOMAIN_MEMB, + SMB_CI_DOMAIN_NAME, + SMB_CI_DOMAIN_SRV, + + SMB_CI_WINS_SRV1, + SMB_CI_WINS_SRV2, + SMB_CI_WINS_EXCL, + + SMB_CI_SRVSVC_SHRSET_ENABLE, + SMB_CI_LOGR_ENABLE, + SMB_CI_MLRPC_KALIVE, + + SMB_CI_MAX_BUFSIZE, + SMB_CI_MAX_WORKERS, + SMB_CI_MAX_CONNECTIONS, + SMB_CI_KEEPALIVE, + SMB_CI_RESTRICT_ANON, + + SMB_CI_SIGNING_ENABLE, + SMB_CI_SIGNING_REQD, + SMB_CI_SIGNING_CHECK, + + SMB_CI_FLUSH_REQUIRED, + SMB_CI_SYNC_ENABLE, + SMB_CI_DIRSYMLINK_DISABLE, + SMB_CI_ANNONCE_QUOTA, + + SMB_CI_SECURITY, + SMB_CI_NBSCOPE, + SMB_CI_SYS_CMNT, + SMB_CI_LM_LEVEL, + SMB_CI_MSDCS_DISABLE, + + SMB_CI_ADS_ENABLE, + SMB_CI_ADS_USER, + SMB_CI_ADS_PASSWD, + SMB_CI_ADS_DOMAIN, + SMB_CI_ADS_USER_CONTAINER, + SMB_CI_ADS_SITE, + SMB_CI_ADS_IPLOOKUP, + + SMB_CI_DYNDNS_ENABLE, + SMB_CI_DYNDNS_RETRY_COUNT, + SMB_CI_DYNDNS_RETRY_SEC, + + SMB_CI_MACHINE_PASSWD, + SMB_CI_MAX +} smb_cfg_id_t; + +/* SMF helper functions */ +extern smb_scfhandle_t *smb_smf_scf_init(char *); +extern void smb_smf_scf_fini(smb_scfhandle_t *); +extern int smb_smf_start_transaction(smb_scfhandle_t *); +extern int smb_smf_end_transaction(smb_scfhandle_t *); +extern int smb_smf_set_string_property(smb_scfhandle_t *, char *, char *); +extern int smb_smf_get_string_property(smb_scfhandle_t *, char *, + char *, size_t); +extern int smb_smf_set_integer_property(smb_scfhandle_t *, char *, int64_t); +extern int smb_smf_get_integer_property(smb_scfhandle_t *, char *, int64_t *); +extern int smb_smf_set_boolean_property(smb_scfhandle_t *, char *, uint8_t); +extern int smb_smf_get_boolean_property(smb_scfhandle_t *, char *, uint8_t *); +extern int smb_smf_set_opaque_property(smb_scfhandle_t *, char *, + void *, size_t); +extern int smb_smf_get_opaque_property(smb_scfhandle_t *, char *, + void *, size_t); +extern int smb_smf_create_service_pgroup(smb_scfhandle_t *, char *); +extern int smb_smf_delete_service_pgroup(smb_scfhandle_t *, char *); +extern int smb_smf_create_instance_pgroup(smb_scfhandle_t *, char *); +extern int smb_smf_delete_instance_pgroup(smb_scfhandle_t *, char *); +extern int smb_smf_delete_property(smb_scfhandle_t *, char *); +extern int smb_smf_instance_exists(smb_scfhandle_t *, char *); +extern int smb_smf_instance_create(smb_scfhandle_t *, char *, char *); +extern int smb_smf_instance_delete(smb_scfhandle_t *, char *); +extern smb_scfhandle_t *smb_smf_get_iterator(char *); +extern int smb_smf_get_property(smb_scfhandle_t *, int, char *, char *, + size_t); +extern int smb_smf_set_property(smb_scfhandle_t *, int, char *, char *); + +/* Configuration management functions */ +extern int smb_config_load(void); +extern void smb_config_rdlock(void); +extern void smb_config_wrlock(void); +extern void smb_config_unlock(void); +extern char *smb_config_get(smb_cfg_id_t); +extern char *smb_config_getstr(smb_cfg_id_t); +extern int smb_config_getyorn(smb_cfg_id_t); +extern uint32_t smb_config_getnum(smb_cfg_id_t); + +/* + * smb_config_getenv + * + * Retrieves the property value from SMF. + * Caller must free the returned buffer. + * + */ +extern char *smb_config_getenv(smb_cfg_id_t id); + +extern int smb_config_set(smb_cfg_id_t, char *); +extern int smb_config_setnum(smb_cfg_id_t, uint32_t); +extern uint8_t smb_config_get_fg_flag(void); +extern int smb_config_setenv(smb_cfg_id_t id, char *); +extern char *smb_config_get_localsid(void); +extern int smb_config_secmode_fromstr(char *secmode); +extern char *smb_config_secmode_tostr(int secmode); +extern int smb_config_get_secmode(void); +extern int smb_config_set_secmode(int secmode); +extern int smb_config_set_idmap_domain(char *value); +extern int smb_config_set_idmap_gc(char *value); +extern int smb_config_refresh_idmap(void); + +/* smb_door_client.c */ +typedef struct smb_joininfo { + char domain_name[SMB_PI_MAX_DOMAIN]; + char domain_username[BUF_LEN + 1]; + char domain_passwd[BUF_LEN + 1]; + uint32_t mode; +} smb_joininfo_t; + +/* APIs to communicate with SMB daemon via door calls */ +extern int smbd_set_param(smb_cfg_id_t, char *); +extern int smbd_get_param(smb_cfg_id_t, char *); +extern int smbd_get_security_mode(int *); +extern int smbd_netbios_reconfig(void); +extern uint32_t smb_join(smb_joininfo_t *info); + + +#define SMB_DOMAIN_NOMACHINE_SID -1 +#define SMB_DOMAIN_NODOMAIN_SID -2 + +extern int nt_domain_init(char *resource_domain, uint32_t secmode); + +/* Following set of functions, manipulate WINS server configuration */ +extern int smb_wins_allow_list(char *config_list, char *allow_list); +extern int smb_wins_exclude_list(char *config_list, char *exclude_list); +extern boolean_t smb_wins_is_excluded(in_addr_t ipaddr, + unsigned long *exclude_list, int nexclude); +extern void smb_wins_build_list(char *buf, uint32_t iplist[], int max_naddr); +extern int smb_wins_iplist(char *list, uint32_t iplist[], int max_naddr); + +/* + * Information on a particular domain: the domain name, the + * name of a controller (PDC or BDC) and it's ip address. + */ +typedef struct smb_ntdomain { + char domain[SMB_PI_MAX_DOMAIN_U]; + char server[SMB_PI_MAX_DOMAIN_U]; + uint32_t ipaddr; +} smb_ntdomain_t; + +/* SMB domain information management functions */ +extern void smb_purge_domain_info(void); +extern int smb_is_domain_member(void); +extern uint8_t smb_get_fg_flag(void); +extern void smb_set_domain_member(int set); +extern smb_ntdomain_t *smb_getdomaininfo(uint32_t timeout); +extern void smb_setdomaininfo(char *domain, char *server, uint32_t ipaddr); +extern void smb_logdomaininfo(smb_ntdomain_t *di); +extern uint32_t smb_get_security_mode(void); + +extern int nt_priv_presentable_num(void); + +/* + * Following set of function, handle calls to SMB Kernel driver, via + * Kernel doors interface. + */ +extern uint64_t smb_dwncall_user_num(void); +extern int smb_dwncall_share(int, char *, char *); + +/* + * buffer context structure. This is used to keep track of the buffer + * context. + * + * basep: points to the beginning of the buffer + * curp: points to the current offset + * endp: points to the limit of the buffer + */ +typedef struct { + unsigned char *basep; + unsigned char *curp; + unsigned char *endp; +} smb_ctxbuf_t; + +extern int smb_ctxbuf_init(smb_ctxbuf_t *ctx, unsigned char *buf, + size_t buflen); +extern int smb_ctxbuf_len(smb_ctxbuf_t *ctx); +extern int smb_ctxbuf_printf(smb_ctxbuf_t *ctx, const char *fmt, ...); + +/* Functions to handle SMB daemon communications with idmap service */ +extern int smb_idmap_start(void); +extern void smb_idmap_stop(void); +extern int smb_idmap_restart(void); + +/* Miscellaneous functions */ +extern void hexdump(unsigned char *, int); +extern size_t bintohex(const char *, size_t, char *, size_t); +extern size_t hextobin(const char *, size_t, char *, size_t); +extern char *trim_whitespace(char *buf); +extern void randomize(char *, unsigned); +extern void rand_hash(unsigned char *, size_t, unsigned char *, size_t); + +extern int smb_getdomainname(char *, size_t); +extern int smb_getfqhostname(char *, size_t); +extern int smb_gethostname(char *, size_t, int); +extern int smb_getnetbiosname(char *, size_t); + +void smb_trace(const char *s); +void smb_tracef(const char *fmt, ...); + +/* + * Authentication + */ + +#define SMBAUTH_LM_MAGIC_STR "KGS!@#$%" + +#define SMBAUTH_HASH_SZ 16 /* also LM/NTLM/NTLMv2 Hash size */ +#define SMBAUTH_LM_RESP_SZ 24 /* also NTLM Response size */ +#define SMBAUTH_LM_PWD_SZ 14 /* LM password size */ +#define SMBAUTH_V2_CLNT_CHALLENGE_SZ 8 /* both LMv2 and NTLMv2 */ +#define SMBAUTH_SESSION_KEY_SZ SMBAUTH_HASH_SZ +#define SMBAUTH_HEXHASH_SZ (SMBAUTH_HASH_SZ * 2) + +#define SMBAUTH_FAILURE 1 +#define SMBAUTH_SUCCESS 0 +#define MD_DIGEST_LEN 16 + +/* + * Name Types + * + * The list of names near the end of the data blob (i.e. the ndb_names + * field of the smb_auth_data_blob_t data structure) can be classify into + * the following types: + * + * 0x0000 Indicates the end of the list. + * 0x0001 The name is a NetBIOS machine name (e.g. server name) + * 0x0002 The name is an NT Domain NetBIOS name. + * 0x0003 The name is the server's DNS hostname. + * 0x0004 The name is a W2K Domain name (a DNS name). + */ +#define SMBAUTH_NAME_TYPE_LIST_END 0x0000 +#define SMBAUTH_NAME_TYPE_SERVER_NETBIOS 0x0001 +#define SMBAUTH_NAME_TYPE_DOMAIN_NETBIOS 0x0002 +#define SMBAUTH_NAME_TYPE_SERVER_DNS 0x0003 +#define SMBAUTH_NAME_TYPE_DOMAIN_DNS 0x0004 + +/* + * smb_auth_name_entry_t + * + * Each name entry in the data blob consists of the following 3 fields: + * + * nne_type - name type + * nne_len - the length of the name + * nne_name - the name, in uppercase UCS-2LE Unicode format + */ +typedef struct smb_auth_name_entry { + unsigned short nne_type; + unsigned short nne_len; + mts_wchar_t nne_name[SMB_PI_MAX_DOMAIN * 2]; +} smb_auth_name_entry_t; + +/* + * smb_auth_data_blob + * + * The format of this NTLMv2 data blob structure is as follow: + * + * - Blob Signature 0x01010000 (4 bytes) + * - Reserved (0x00000000) (4 bytes) + * - Timestamp Little-endian, 64-bit signed value representing + * the number of tenths of a microsecond since January 1, 1601. + * (8 bytes) + * - Client Challenge (8 bytes) + * - Unknown1 (4 bytes) + * - List of Target Information (variable length) + * - Unknown2 (4 bytes) + */ +typedef struct smb_auth_data_blob { + unsigned char ndb_signature[4]; + unsigned char ndb_reserved[4]; + uint64_t ndb_timestamp; + unsigned char ndb_clnt_challenge[SMBAUTH_V2_CLNT_CHALLENGE_SZ]; + unsigned char ndb_unknown[4]; + smb_auth_name_entry_t ndb_names[2]; + unsigned char ndb_unknown2[4]; +} smb_auth_data_blob_t; + +#define SMBAUTH_BLOB_MAXLEN (sizeof (smb_auth_data_blob_t)) +#define SMBAUTH_CI_MAXLEN SMBAUTH_LM_RESP_SZ +#define SMBAUTH_CS_MAXLEN (SMBAUTH_BLOB_MAXLEN + SMBAUTH_HASH_SZ) + +/* + * smb_auth_info_t + * + * The structure contains all the authentication information + * needed for the preparaton of the SMBSessionSetupAndx request + * and the user session key. + * + * hash - NTLM hash + * hash_v2 - NTLMv2 hash + * ci_len - the length of the case-insensitive password + * ci - case-insensitive password + * (If NTLMv2 authentication mechanism is used, it + * represents the LMv2 response. Otherwise, it + * is empty.) + * cs_len - the length of the case-sensitive password + * cs - case-sensitive password + * (If NTLMv2 authentication mechanism is used, it + * represents the NTLMv2 response. Otherwise, it + * represents the NTLM response.) + * data_blob - NTLMv2 data blob + */ +typedef struct smb_auth_info { + unsigned char hash[SMBAUTH_HASH_SZ]; + unsigned char hash_v2[SMBAUTH_HASH_SZ]; + unsigned short ci_len; + unsigned char ci[SMBAUTH_CI_MAXLEN]; + unsigned short cs_len; + unsigned char cs[SMBAUTH_CS_MAXLEN]; + int lmcompatibility_lvl; + smb_auth_data_blob_t data_blob; +} smb_auth_info_t; + +extern int smb_getdomainname(char *, size_t); +extern int smb_getfqhostname(char *, size_t); +extern int smb_gethostname(char *, size_t, int); +extern int smb_getnetbiosname(char *, size_t); + +void smb_trace(const char *s); +void smb_tracef(const char *fmt, ...); + +/* + * SMB password management + */ + +#define SMB_PWF_LM 0x01 /* LM hash is present */ +#define SMB_PWF_NT 0x02 /* NT hash is present */ +#define SMB_PWF_DISABLE 0x04 /* Account is disabled */ + +typedef struct smb_passwd { + uid_t pw_uid; + uint32_t pw_flags; + unsigned char pw_lmhash[SMBAUTH_HASH_SZ]; + unsigned char pw_nthash[SMBAUTH_HASH_SZ]; +} smb_passwd_t; + +/* + * Control flags passed to smb_pwd_setcntl + */ +#define SMB_PWC_DISABLE 0x01 +#define SMB_PWC_ENABLE 0x02 +#define SMB_PWC_NOLM 0x04 + +#define SMB_PWE_SUCCESS 0 +#define SMB_PWE_USER_UNKNOWN 1 +#define SMB_PWE_USER_DISABLE 2 +#define SMB_PWE_CLOSE_FAILED 3 +#define SMB_PWE_OPEN_FAILED 4 +#define SMB_PWE_WRITE_FAILED 6 +#define SMB_PWE_UPDATE_FAILED 7 +#define SMB_PWE_STAT_FAILED 8 +#define SMB_PWE_BUSY 9 +#define SMB_PWE_DENIED 10 +#define SMB_PWE_SYSTEM_ERROR 11 +#define SMB_PWE_MAX 12 + +extern smb_passwd_t *smb_pwd_getpasswd(const char *, smb_passwd_t *); +extern int smb_pwd_setpasswd(const char *, const char *); +extern int smb_pwd_setcntl(const char *, int); + +extern int smb_auth_qnd_unicode(mts_wchar_t *dst, char *src, int length); +extern int smb_auth_hmac_md5(unsigned char *data, int data_len, + unsigned char *key, int key_len, unsigned char *digest); + +/* + * A variation on HMAC-MD5 known as HMACT64 is used by Windows systems. + * The HMACT64() function is the same as the HMAC-MD5() except that + * it truncates the input key to 64 bytes rather than hashing it down + * to 16 bytes using the MD5() function. + */ +#define SMBAUTH_HMACT64(D, Ds, K, Ks, digest) \ + smb_auth_hmac_md5(D, Ds, K, (Ks > 64) ? 64 : Ks, digest) + +extern int smb_auth_DES(unsigned char *, int, unsigned char *, int, + unsigned char *, int); + +extern int smb_auth_md4(unsigned char *, unsigned char *, int); +extern int smb_auth_lm_hash(char *, unsigned char *); +extern int smb_auth_ntlm_hash(char *, unsigned char *); + +extern int smb_auth_set_info(char *, char *, + unsigned char *, char *, unsigned char *, + int, int, smb_auth_info_t *); + +extern int smb_auth_gen_session_key(smb_auth_info_t *, unsigned char *); + +boolean_t smb_auth_validate_lm(unsigned char *, uint32_t, smb_passwd_t *, + unsigned char *, int, char *); +boolean_t smb_auth_validate_nt(unsigned char *, uint32_t, smb_passwd_t *, + unsigned char *, int, char *); + +/* + * SMB MAC Signing + */ + +#define SMB_MAC_KEY_SZ (SMBAUTH_SESSION_KEY_SZ + SMBAUTH_CS_MAXLEN) +#define SMB_SIG_OFFS 14 /* signature field offset within header */ +#define SMB_SIG_SIZE 8 /* SMB signature size */ + +/* + * Signing flags: + * + * SMB_SCF_ENABLE Signing is enabled. + * + * SMB_SCF_REQUIRED Signing is enabled and required. + * This flag shouldn't be set if + * SMB_SCF_ENABLE isn't set. + * + * SMB_SCF_STARTED Signing will start after receiving + * the first non-anonymous SessionSetup + * request. + * + * SMB_SCF_KEY_ISSET_THIS_LOGON Indicates whether the MAC key has just + * been set for this logon. (prior to + * sending the SMBSessionSetup request) + * + */ +#define SMB_SCF_ENABLE 0x01 +#define SMB_SCF_REQUIRED 0x02 +#define SMB_SCF_STARTED 0x04 +#define SMB_SCF_KEY_ISSET_THIS_LOGON 0x08 + +/* + * smb_sign_ctx + * + * SMB signing context. + * + * ssc_seqnum sequence number + * ssc_keylen mac key length + * ssc_mid multiplex id - reserved + * ssc_flags flags + * ssc_mackey mac key + * ssc_sign mac signature + * + */ +typedef struct smb_sign_ctx { + unsigned int ssc_seqnum; + unsigned short ssc_keylen; + unsigned short ssc_mid; + unsigned int ssc_flags; + unsigned char ssc_mackey[SMB_MAC_KEY_SZ]; + unsigned char ssc_sign[SMB_SIG_SIZE]; +} smb_sign_ctx_t; + +extern int smb_mac_init(smb_sign_ctx_t *sign_ctx, smb_auth_info_t *auth); +extern int smb_mac_calc(smb_sign_ctx_t *sign_ctx, + const unsigned char *buf, size_t buf_len, unsigned char *mac_sign); +extern int smb_mac_chk(smb_sign_ctx_t *sign_ctx, + const unsigned char *buf, size_t buf_len); +extern int smb_mac_sign(smb_sign_ctx_t *sign_ctx, + unsigned char *buf, size_t buf_len); +extern void smb_mac_inc_seqnum(smb_sign_ctx_t *sign_ctx); +extern void smb_mac_dec_seqnum(smb_sign_ctx_t *sign_ctx); + +/* + * Each domain is categorized using the enum values below. + * The local domain refers to the local machine and is named + * after the local hostname. The primary domain is the domain + * that the system joined. All other domains are either + * trusted or untrusted, as defined by the primary domain PDC. + * + * This enum must be kept in step with the table of strings + * in ntdomain.c. + */ +typedef enum nt_domain_type { + NT_DOMAIN_NULL, + NT_DOMAIN_BUILTIN, + NT_DOMAIN_LOCAL, + NT_DOMAIN_PRIMARY, + NT_DOMAIN_ACCOUNT, + NT_DOMAIN_TRUSTED, + NT_DOMAIN_UNTRUSTED, + NT_DOMAIN_NUM_TYPES +} nt_domain_type_t; + + +/* + * This is the information that is held about each domain. The database + * is a linked list that is threaded through the domain structures. As + * the number of domains in the database should be small (32 max), this + * should be sufficient. + */ +typedef struct nt_domain { + struct nt_domain *next; + nt_domain_type_t type; + char *name; + nt_sid_t *sid; +} nt_domain_t; + +nt_domain_t *nt_domain_new(nt_domain_type_t type, char *name, nt_sid_t *sid); +void nt_domain_delete(nt_domain_t *domain); +nt_domain_t *nt_domain_add(nt_domain_t *new_domain); +void nt_domain_remove(nt_domain_t *domain); +void nt_domain_flush(nt_domain_type_t domain_type); +void nt_domain_sync(void); +char *nt_domain_xlat_type(nt_domain_type_t domain_type); +nt_domain_type_t nt_domain_xlat_type_name(char *type_name); +nt_domain_t *nt_domain_lookup_name(char *domain_name); +nt_domain_t *nt_domain_lookup_sid(nt_sid_t *domain_sid); +nt_domain_t *nt_domain_lookupbytype(nt_domain_type_t type); +nt_sid_t *nt_domain_local_sid(void); + +#define SMB_GROUP_PER_LIST 5 + +/* + * This structure takes different args passed from the client/server routines + * of the SMB local group door service. Extend this structure if a new type + * client paramater needs to be passed. + */ +typedef struct ntgrp_dr_arg { + char *gname; + char *desc; + char *member; + char *newgname; + uint32_t privid; + uint32_t priv_attr; + int offset; + char *scope; + int type; + int count; + uint32_t ntstatus; +} ntgrp_dr_arg_t; + +typedef struct ntgrp { + DWORD rid; /* Rid of the group */ + char *name; /* Name of the group */ + char *desc; /* Desc of gruup */ + char *type; /* sid_name_use */ + char *sid; /* Sid */ + DWORD attr; /* Attribute */ +} ntgrp_t; + +typedef struct ntgrp_list { + int cnt; + ntgrp_t groups[SMB_GROUP_PER_LIST]; +} ntgrp_list_t; + +typedef char *members_list; +typedef struct ntgrp_member_list { + DWORD rid; /* Rid of the group in which members belong */ + int cnt; /* members */ + members_list members[SMB_GROUP_PER_LIST]; +} ntgrp_member_list_t; + +typedef struct ntpriv { + DWORD id; /* Id of priv */ + char *name; /* Name of priv */ +} ntpriv_t; +typedef ntpriv_t *privs_t; + +typedef struct ntpriv_list { + int cnt; /* Number of privs */ + privs_t privs[ANY_SIZE_ARRAY]; /* privs only presentable ones */ +} ntpriv_list_t; + + +/* the xdr functions */ +extern bool_t xdr_ntgrp_dr_arg_t(XDR *, ntgrp_dr_arg_t *); +extern bool_t xdr_ntgrp_t(XDR *, ntgrp_t *); +extern bool_t xdr_ntgrp_list_t(XDR *, ntgrp_list_t *); +extern bool_t xdr_members_list(XDR *, members_list *); +extern bool_t xdr_ntgrp_member_list_t(XDR *, ntgrp_member_list_t *); +extern bool_t xdr_ntpriv_t(XDR *, ntpriv_t *); +extern bool_t xdr_privs_t(XDR *, privs_t *); +extern bool_t xdr_ntpriv_list_t(XDR *, ntpriv_list_t *); + +extern void smb_group_free_memberlist(ntgrp_member_list_t *, int); +extern void smb_group_free_list(ntgrp_list_t *, int); +extern void smb_group_free_privlist(ntpriv_list_t *, int); + +extern uint32_t smb_group_add(char *, char *); +extern uint32_t smb_group_modify(char *, char *, char *); +extern uint32_t smb_group_delete(char *); +extern uint32_t smb_group_member_remove(char *, char *); +extern uint32_t smb_group_member_add(char *, char *); +extern uint32_t smb_group_priv_num(int *); +extern uint32_t smb_group_priv_list(ntpriv_list_t **); +extern uint32_t smb_group_priv_get(char *, uint32_t, uint32_t *); +extern uint32_t smb_group_priv_set(char *, uint32_t, uint32_t); +extern uint32_t smb_group_count(int *); +extern uint32_t smb_group_list(int, ntgrp_list_t **, char *, int); +extern uint32_t smb_group_member_count(char *, int *); +extern uint32_t smb_group_member_list(char *, int, ntgrp_member_list_t **); + +extern char *smb_dr_encode_grp_privlist(uint32_t, ntpriv_list_t *, size_t *); +extern ntpriv_list_t *smb_dr_decode_grp_privlist(char *, size_t); + +extern char *smb_dr_encode_grp_list(uint32_t, ntgrp_list_t *, size_t *); +extern ntgrp_list_t *smb_dr_decode_grp_list(char *, size_t); + +extern char *smb_dr_encode_grp_memberlist(uint32_t, ntgrp_member_list_t *, + size_t *); +extern ntgrp_member_list_t *smb_dr_decode_grp_memberlist(char *buf, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSMB_H */ diff --git a/usr/src/lib/smbsrv/libsmb/common/llib-lsmb b/usr/src/lib/smbsrv/libsmb/common/llib-lsmb new file mode 100644 index 0000000000..e900ce8047 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/llib-lsmb @@ -0,0 +1,31 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include <smbsrv/libsmb.h> diff --git a/usr/src/lib/smbsrv/libsmb/common/mapfile-vers b/usr/src/lib/smbsrv/libsmb/common/mapfile-vers new file mode 100644 index 0000000000..05c89f93ff --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/mapfile-vers @@ -0,0 +1,325 @@ +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +SUNWprivate { + global: + bintohex; + codepage_islower; + codepage_isupper; + codepage_tolower; + codepage_toupper; + hexdump; + hextobin; + ht_add_item; + ht_clean_table; + ht_clear_delete; + ht_create_table; + ht_destroy_table; + ht_find_item; + ht_findfirst; + ht_findnext; + ht_findnext; + ht_get_total_items; + ht_mark_delete; + ht_register_callback; + ht_remove_item; + ht_replace_item; + ht_set_cmpfn; + smb_msgbuf_base; + smb_msgbuf_decode; + smb_msgbuf_dword_align; + smb_msgbuf_encode; + smb_msgbuf_fclear; + smb_msgbuf_fset; + smb_msgbuf_has_space; + smb_msgbuf_init; + smb_msgbuf_size; + smb_msgbuf_term; + smb_msgbuf_used; + smb_msgbuf_word_align; + mts_mbstos; + mts_mbstowcs; + mts_mbtowc; + mts_sbequiv_strlen; + mts_stombs; + mts_wcequiv_strlen; + mts_wcstombs; + mts_wctomb; + netr_client_mkabsolute; + nt_builtin_findfirst; + nt_builtin_findnext; + nt_builtin_fini; + nt_builtin_init; + nt_builtin_is_wellknown; + nt_builtin_lookup; + nt_builtin_lookup_domain; + nt_builtin_lookup_name; + nt_builtin_lookup_sid; + nt_domain_add; + nt_domain_flush; + nt_domain_init; + nt_domain_local_sid; + nt_domain_lookup_name; + nt_domain_lookup_sid; + nt_domain_lookupbytype; + nt_domain_new; + nt_sid_dup; + nt_sid_format2; + nt_sid_format; + nt_sid_gen_null_sid; + nt_sid_get_rid; + nt_sid_is_builtin; + nt_sid_is_equal; + nt_sid_is_indomain; + nt_sid_is_local; + nt_sid_is_valid; + nt_sid_length; + nt_sid_logf; + nt_sid_name_use; + nt_sid_splice; + nt_sid_split; + nt_sid_strtosid; + oem_get_smb_cpid; + oem_get_telnet_cpid; + oem_language_set; + oemstounicodes; + rand_hash; + randomize; + smb_auth_DES; + smb_auth_gen_session_key; + smb_auth_ntlm_hash; + smb_auth_qnd_unicode; + smb_auth_set_info; + smb_auth_validate_lm; + smb_auth_validate_nt; + smb_config_get; + smb_config_get_fg_flag; + smb_config_get_localsid; + smb_config_get_secmode; + smb_config_getenv; + smb_config_getnum; + smb_config_getstr; + smb_config_getyorn; + smb_config_load; + smb_config_rdlock; + smb_config_refresh_idmap; + smb_config_secmode_fromstr; + smb_config_secmode_tostr; + smb_config_set; + smb_config_set_idmap_domain; + smb_config_set_idmap_gc; + smb_config_set_secmode; + smb_config_setenv; + smb_config_setnum; + smb_config_unlock; + smb_config_wrlock; + smb_ctxbuf_init; + smb_ctxbuf_len; + smb_ctxbuf_printf; + smb_dr_decode_arg_get_token; + smb_dr_decode_common; + smb_dr_decode_finish; + smb_dr_decode_grp_list; + smb_dr_decode_grp_memberlist; + smb_dr_decode_grp_privlist; + smb_dr_decode_start; + smb_dr_decode_string; + smb_dr_encode_common; + smb_dr_encode_finish; + smb_dr_encode_grp_list; + smb_dr_encode_grp_memberlist; + smb_dr_encode_grp_privlist; + smb_dr_encode_res_token; + smb_dr_encode_start; + smb_dr_encode_string; + smb_dr_free_string; + smb_dr_get_BYTE; + smb_dr_get_buf; + smb_dr_get_dword; + smb_dr_get_int32; + smb_dr_get_lmshare; + smb_dr_get_lmshr_iterator; + smb_dr_get_lmshr_list; + smb_dr_get_opcode; + smb_dr_get_res_stat; + smb_dr_get_short; + smb_dr_get_string; + smb_dr_get_uint32; + smb_dr_get_uint64; + smb_dr_get_ushort; + smb_dr_get_word; + smb_dr_put_BYTE; + smb_dr_put_buf; + smb_dr_put_dword; + smb_dr_put_int32; + smb_dr_put_kconfig; + smb_dr_put_lmshare; + smb_dr_put_lmshr_iterator; + smb_dr_put_lmshr_list; + smb_dr_put_short; + smb_dr_put_string; + smb_dr_put_uint32; + smb_dr_put_uint64; + smb_dr_put_ushort; + smb_dr_put_word; + smb_dr_set_opcode; + smb_dr_set_res_stat; + smb_dr_ulist_free; + smb_dwncall_get_users; + smb_dwncall_install_callback; + smb_dwncall_share; + smb_dwncall_user_num; + smb_get_fg_flag; + smb_get_security_mode; + smb_getdomaininfo; + smb_getdomainname; + smb_getfqhostname; + smb_gethostname; + smb_getnetbiosname; + smb_group_add; + smb_group_count; + smb_group_delete; + smb_group_free_list; + smb_group_free_memberlist; + smb_group_free_privlist; + smb_group_list; + smb_group_member_add; + smb_group_member_count; + smb_group_member_list; + smb_group_member_remove; + smb_group_modify; + smb_group_priv_get; + smb_group_priv_list; + smb_group_priv_num; + smb_group_priv_set; + smb_idmap_batch_create; + smb_idmap_batch_destroy; + smb_idmap_batch_getid; + smb_idmap_batch_getmappings; + smb_idmap_batch_getsid; + smb_idmap_getsid; + smb_idmap_restart; + smb_idmap_start; + smb_idmap_stop; + smb_is_domain_member; + smb_join; + smb_load_kconfig; + smb_mac_chk; + smb_mac_dec_seqnum; + smb_mac_inc_seqnum; + smb_mac_init; + smb_mac_sign; + smb_match83; + smb_match; + smb_match_ci; + smb_priv_getbyname; + smb_priv_getbyvalue; + smb_priv_presentable_ids; + smb_priv_presentable_num; + smb_privset_copy; + smb_privset_enable; + smb_privset_free; + smb_privset_init; + smb_privset_log; + smb_privset_new; + smb_privset_query; + smb_privset_size; + smb_privset_validate; + smb_purge_domain_info; + smb_trace; + smb_tracef; + xdr_ntgrp_dr_arg_t; + xdr_ntgrp_list_t; + xdr_ntgrp_member_list_t; + xdr_ntpriv_list_t; + smb_pwd_getpasswd; + smb_pwd_setcntl; + smb_pwd_setpasswd; + smb_set_domain_member; + smb_setdomaininfo; + smb_smf_create_instance_pgroup; + smb_smf_create_service_pgroup; + smb_smf_delete_instance_pgroup; + smb_smf_delete_property; + smb_smf_delete_service_pgroup; + smb_smf_end_transaction; + smb_smf_get_boolean_property; + smb_smf_get_integer_property; + smb_smf_get_iterator; + smb_smf_get_opaque_property; + smb_smf_get_string_property; + smb_smf_instance_create; + smb_smf_instance_delete; + smb_smf_instance_exists; + smb_smf_scf_fini; + smb_smf_scf_init; + smb_smf_set_boolean_property; + smb_smf_set_integer_property; + smb_smf_set_opaque_property; + smb_smf_set_string_property; + smb_smf_start_transaction; + smb_token_log; + smb_token_mkselfrel; + smb_token_print; + smb_token_query_privilege; + smb_trace; + smb_tracef; + smb_wins_allow_list; + smb_wins_build_list; + smb_wins_exclude_list; + smb_wins_iplist; + smb_wins_is_excluded; + smbd_get_param; + smbd_get_security_mode; + smbd_netbios_reconfig; + smbd_set_param; + smbnative_lm_value; + smbnative_os_value; + smbnative_pdc_value; + strcanon; + strsep; + strsubst; + trim_whitespace; + unicodestooems; + utf8_isstrascii; + utf8_isstrlwr; + utf8_isstrupr; + utf8_strcasecmp; + utf8_strlwr; + utf8_strncasecmp; + utf8_strupr; + xdr_ntgrp_dr_arg_t; + xdr_ntgrp_list_t; + xdr_ntgrp_member_list_t; + xdr_ntpriv_list_t; + xdr_smb_dr_bytes_t; + xdr_smb_dr_string_t; + xdr_smb_dr_ulist_t; + xdr_smb_dr_user_ctx_t; + xlate_nt_status; + local: + *; +}; diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_api_door_calls.c b/usr/src/lib/smbsrv/libsmb/common/smb_api_door_calls.c new file mode 100644 index 0000000000..4a84b5d462 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_api_door_calls.c @@ -0,0 +1,864 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Door calls invoked by CLIs to obtain various SMB door service provided + * by SMB daemon. + */ + +#include <syslog.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <unistd.h> +#include <smbsrv/libsmb.h> +#include <smbsrv/wintypes.h> +#include <smbsrv/smb_door_svc.h> +#include <smbsrv/smb_common_door.h> + +/* indexed via opcode (smb_dr_opcode_t) */ +char *smbapi_desc[] = { + "", + "", + "", + "", + "SmbapiUserList", + "SmbGroupAdd", + "SmbGroupDelete", + "SmbGroupAddMember", + "SmbGroupRemoveMember", + "SmbGroupGetCount", + "SmbGroupGetCacheSize", + "SmbGroupModify", + "SmbGroupPresentablePrivNum", + "SmbGroupPresentablePriv", + "SmbGroupGetPriv", + "SmbGroupSetPriv", + "SmbGroupListGroups", + "SmbGroupListMembers", + "SmbGroupMembersCount", + 0 +}; + +/* + * This function will return information on the connected users + * starting at the given offset. + * + * At most 50 users (i.e. SMB_DR_MAX_USER) will be returned via this + * function. Multiple calls might be needed to obtain all connected + * users. + * + * smb_dr_ulist_free must be called to free memory allocated for the + * account and workstation fields of each user in the returned list. + */ +int +smb_api_ulist(int offset, smb_dr_ulist_t *users) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + int rc = -1; + uint_t opcode = SMB_DR_USER_LIST; + int fd; + + bzero(users, sizeof (smb_dr_ulist_t)); + buf = smb_dr_encode_common(opcode, &offset, xdr_uint32_t, &buflen); + if (!buf) + return (-1); + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (-1); + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + if (rbufp) { + rc = smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_smb_dr_ulist_t, users); + + } + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +/* Routines for SMB Group Door Client APIs */ +uint32_t +smb_group_add(char *gname, char *desc) +{ + ntgrp_dr_arg_t *args; + char *buf, *rbufp; + size_t buflen, rbufsize; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_ADD; + int fd; + + if ((gname == 0) || (*gname == 0)) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((args = (ntgrp_dr_arg_t *)malloc(sizeof (ntgrp_dr_arg_t))) == 0) { + syslog(LOG_ERR, "%s: cannot allocate memory", + smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_NO_MEMORY); + } + bzero(args, sizeof (ntgrp_dr_arg_t)); + args->gname = gname; + args->desc = desc; + if ((buf = smb_dr_encode_common(opcode, args, + xdr_ntgrp_dr_arg_t, &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + free(args); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + free(args); + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, &rc) != 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_delete(char *gname) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_DELETE; + int fd; + + if ((gname == 0) || (*gname == 0)) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((buf = smb_dr_encode_string(opcode, gname, &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, &rc) != 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_member_add(char *gname, char *member) +{ + ntgrp_dr_arg_t *args; + char *buf, *rbufp; + size_t buflen, rbufsize; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_MEMBER_ADD; + int fd; + + if ((gname == 0) || (*gname == 0) || + (member == 0) || (*member == 0)) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((args = (ntgrp_dr_arg_t *)malloc(sizeof (ntgrp_dr_arg_t))) == 0) { + syslog(LOG_ERR, "%s: cannot allocate memory", + smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_NO_MEMORY); + } + bzero(args, sizeof (ntgrp_dr_arg_t)); + args->gname = gname; + args->member = member; + if ((buf = smb_dr_encode_common(opcode, args, xdr_ntgrp_dr_arg_t, + &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + free(args); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + free(args); + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, &rc) != 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_member_remove(char *gname, char *member) +{ + ntgrp_dr_arg_t *args; + char *buf, *rbufp; + size_t buflen, rbufsize; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_MEMBER_REMOVE; + int fd; + + if ((gname == 0) || (*gname == 0) || + (member == 0) || (*member == 0)) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((args = (ntgrp_dr_arg_t *)malloc(sizeof (ntgrp_dr_arg_t))) == 0) { + syslog(LOG_ERR, "%s: cannot allocate memory", + smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_NO_MEMORY); + } + bzero(args, sizeof (ntgrp_dr_arg_t)); + args->gname = gname; + args->member = member; + if ((buf = smb_dr_encode_common(opcode, args, xdr_ntgrp_dr_arg_t, + &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + free(args); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + free(args); + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, &rc) != 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + + +uint32_t +smb_group_count(int *cnt) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + uint_t opcode = SMB_DR_GROUP_COUNT; + int fd; + + if (cnt == 0) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + *cnt = 0; + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, + smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + if ((buf = smb_dr_set_opcode(opcode, &buflen)) == 0) { + (void) close(fd); + return (NT_STATUS_INVALID_PARAMETER); + } + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, cnt) != 0) { + (void) close(fd); + return (NT_STATUS_INVALID_PARAMETER); + } + rc = NT_STATUS_SUCCESS; + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_cachesize(int *sz) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + uint_t opcode = SMB_DR_GROUP_CACHE_SIZE; + int fd; + + if (sz == 0) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + *sz = 0; + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, + smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + if ((buf = smb_dr_set_opcode(opcode, &buflen)) == 0) { + (void) close(fd); + return (NT_STATUS_INVALID_PARAMETER); + } + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, sz) != 0) { + (void) close(fd); + return (NT_STATUS_INVALID_PARAMETER); + } + rc = NT_STATUS_SUCCESS; + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_modify(char *gname, char *newgname, char *desc) +{ + ntgrp_dr_arg_t *args; + char *buf, *rbufp; + size_t buflen, rbufsize; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_MODIFY; + int fd; + + if ((gname == 0) || (*gname == 0) || + (newgname == 0) || (*newgname == 0)) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((args = (ntgrp_dr_arg_t *)malloc(sizeof (ntgrp_dr_arg_t))) == 0) { + syslog(LOG_ERR, "%s: cannot allocate memory", + smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_NO_MEMORY); + } + bzero(args, sizeof (ntgrp_dr_arg_t)); + args->gname = gname; + args->desc = desc; + args->newgname = newgname; + if ((buf = smb_dr_encode_common(opcode, args, xdr_ntgrp_dr_arg_t, + &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + free(args); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + free(args); + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, &rc) != 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_priv_num(int *num) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_PRIV_NUM; + int fd; + + if (num == 0) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + *num = 0; + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, + smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + if ((buf = smb_dr_set_opcode(opcode, &buflen)) == 0) { + (void) close(fd); + return (NT_STATUS_INVALID_PARAMETER); + } + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, num) != 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + rc = NT_STATUS_SUCCESS; + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_priv_list(ntpriv_list_t **list) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_PRIV_LIST; + int fd; + *list = NULL; + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, + smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + if ((buf = smb_dr_set_opcode(opcode, &buflen)) == 0) { + (void) close(fd); + return (NT_STATUS_INVALID_PARAMETER); + } + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result */ + if (rbufp) { + if ((*list = smb_dr_decode_grp_privlist( + rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET)) == 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + rc = NT_STATUS_SUCCESS; + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_priv_get(char *gname, uint32_t privid, uint32_t *privval) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + ntgrp_dr_arg_t *args; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_PRIV_GET; + int fd; + uint32_t retval; + + *privval = SE_PRIVILEGE_DISABLED; + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((args = (ntgrp_dr_arg_t *)malloc(sizeof (ntgrp_dr_arg_t))) == 0) { + syslog(LOG_ERR, "%s: cannot allocate memory", + smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_NO_MEMORY); + } + bzero(args, sizeof (ntgrp_dr_arg_t)); + args->gname = gname; + args->privid = privid; + if ((buf = smb_dr_encode_common(opcode, args, xdr_ntgrp_dr_arg_t, + &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + free(args); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + free(args); + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, + &retval) != 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + *privval = retval; + rc = NT_STATUS_SUCCESS; + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_priv_set(char *gname, uint32_t privid, uint32_t priv_attr) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + ntgrp_dr_arg_t *args; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_PRIV_SET; + int fd; + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((args = (ntgrp_dr_arg_t *)malloc(sizeof (ntgrp_dr_arg_t))) == 0) { + syslog(LOG_ERR, "%s: cannot allocate memory", + smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_NO_MEMORY); + } + bzero(args, sizeof (ntgrp_dr_arg_t)); + args->gname = gname; + args->privid = privid; + args->priv_attr = priv_attr; + if ((buf = smb_dr_encode_common(opcode, args, xdr_ntgrp_dr_arg_t, + &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + free(args); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + free(args); + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, &rc) != 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_list(int offset, ntgrp_list_t **list, char *scope, int type) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + ntgrp_dr_arg_t *args; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_LIST; + int fd; + *list = NULL; + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((args = (ntgrp_dr_arg_t *)malloc(sizeof (ntgrp_dr_arg_t))) == 0) { + syslog(LOG_ERR, "%s: cannot allocate memory", + smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_NO_MEMORY); + } + bzero(args, sizeof (ntgrp_dr_arg_t)); + args->offset = offset; + args->type = type; + args->scope = scope; + if ((buf = smb_dr_encode_common(opcode, args, xdr_ntgrp_dr_arg_t, + &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + free(args); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + free(args); + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if (rbufp) { + if ((*list = smb_dr_decode_grp_list(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET)) == 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + rc = NT_STATUS_SUCCESS; + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_member_list(char *gname, int offset, ntgrp_member_list_t **members) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + ntgrp_dr_arg_t *args; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_MEMBER_LIST; + int fd; + *members = NULL; + + if ((gname == 0) || (*gname == 0)) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((args = (ntgrp_dr_arg_t *)malloc(sizeof (ntgrp_dr_arg_t))) == 0) { + syslog(LOG_ERR, "%s: cannot allocate memory for ret_mem_list", + smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_NO_MEMORY); + } + bzero(args, sizeof (ntgrp_dr_arg_t)); + args->gname = gname; + args->offset = offset; + if ((buf = smb_dr_encode_common(opcode, args, xdr_ntgrp_dr_arg_t, + &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + free(args); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + free(args); + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if (rbufp) { + if ((*members = smb_dr_decode_grp_memberlist( + rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET)) == 0) { + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + rc = NT_STATUS_SUCCESS; + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + (void) close(fd); + return (rc); +} + +uint32_t +smb_group_member_count(char *gname, int *cnt) +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + ntgrp_dr_arg_t *dec_args; + uint32_t rc = NT_STATUS_UNSUCCESSFUL; + int opcode = SMB_DR_GROUP_MEMBER_COUNT; + int fd; + + if ((gname == 0) || (*gname == 0) || (cnt == 0)) { + syslog(LOG_ERR, "%s: invalid parameter(s)", + smbapi_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + + if (smb_dr_clnt_open(&fd, SMB_DR_SVC_NAME, smbapi_desc[opcode]) == -1) + return (NT_STATUS_INTERNAL_ERROR); + + /* Encode */ + if ((buf = smb_dr_encode_string(opcode, gname, &buflen)) == 0) { + syslog(LOG_ERR, "%s: Encode error", smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_INTERNAL_ERROR); + } + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smbapi_desc[opcode]); + + /* Decode Result. */ + if ((dec_args = (ntgrp_dr_arg_t *) + malloc(sizeof (ntgrp_dr_arg_t))) == 0) { + syslog(LOG_ERR, "%s: cannot allocate memory", + smbapi_desc[opcode]); + (void) close(fd); + return (NT_STATUS_NO_MEMORY); + } + bzero(dec_args, sizeof (ntgrp_dr_arg_t)); + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_ntgrp_dr_arg_t, dec_args) + != 0) { + free(dec_args); + (void) close(fd); + return (dec_args->ntstatus); + } + } + *cnt = dec_args->count; + rc = dec_args->ntstatus; + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + free(dec_args); + (void) close(fd); + return (rc); +} + +/* Helper functions for local group door service to free up data structures */ +void +smb_group_free_privlist(ntpriv_list_t *list, int deletelist) +{ + int i; + if (!list) + return; + if (list->privs != NULL) { + for (i = 0; i < list->cnt; i++) { + if (list->privs[i] != NULL) { + free(list->privs[i]->name); + free(list->privs[i]); + } + } + if (deletelist) + free(list); + } +} + +void +smb_group_free_list(ntgrp_list_t *list, int entries_only) +{ + int i; + + if (!list) { + return; + } + + for (i = 0; i < list->cnt; i++) { + free(list->groups[i].name); + free(list->groups[i].desc); + free(list->groups[i].type); + free(list->groups[i].sid); + } + if (!entries_only) + free(list); +} + +void +smb_group_free_memberlist(ntgrp_member_list_t *members, + int entries_only) +{ + int i; + + if (!members) { + return; + } + + for (i = 0; i < members->cnt; i++) { + free(members->members[i]); + } + if (!entries_only) + free(members); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_auth.c b/usr/src/lib/smbsrv/libsmb/common/smb_auth.c new file mode 100644 index 0000000000..d8950616db --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_auth.c @@ -0,0 +1,706 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <strings.h> +#include <stdlib.h> +#include <smbsrv/alloc.h> +#include <smbsrv/codepage.h> +#include <smbsrv/oem.h> +#include <smbsrv/ctype.h> +#include <smbsrv/crypt.h> +#include <smbsrv/libsmb.h> + +extern void randomize(char *data, unsigned len); +static uint64_t unix_micro_to_nt_time(struct timeval *unix_time); + +/* + * smb_auth_qnd_unicode + * + * Quick and dirty unicode conversion! + * Returns the length of dst in bytes. + */ +int +smb_auth_qnd_unicode(mts_wchar_t *dst, char *src, int length) +{ + int i; + + unsigned int cpid = oem_get_telnet_cpid(); + unsigned int count; + mts_wchar_t new_char; + + if ((count = oemstounicodes(dst, src, length, cpid)) == 0) { + for (i = 0; i < length; ++i) { + new_char = (mts_wchar_t)src[i] & 0xff; + dst[i] = LE_IN16(&new_char); + } + dst[i] = 0; + count = length; + } + + return (count * sizeof (mts_wchar_t)); +} + +/* + * smb_auth_lmupr + * + * Converts the given LM password to all uppercase. + * The standard strupr cannot + * be used here because lm_pwd doesn't have to be + * nul terminated. + */ +static void +smb_auth_lmupr(unsigned char *lm_pwd) +{ + unsigned char *p = lm_pwd; + int i; + + for (i = 0; (*p) && (i < SMBAUTH_LM_PWD_SZ); i++) { + if (mts_isascii(*p)) { + *p = codepage_toupper(*p); + p++; + } + } +} + +/* + * smb_auth_lm_hash + * + * Source: Implementing CIFS (Chris Hertel) + * + * 1. The password, as entered by user, is either padded with nulls + * or trimmed to 14 bytes. + * . Note that the 14-byte result string is not handled as a + * nul-terminated string. + * . The given password is OEM not Unicode + * + * 2. The 14-byte password is converted to all uppercase + * + * 3. The result is used as key to encrypt the KGS magic string to + * make a 16-byte hash. + */ +int +smb_auth_lm_hash(char *password, unsigned char *lm_hash) +{ + unsigned char lm_pwd[SMBAUTH_LM_PWD_SZ]; + + bzero((void *)lm_pwd, SMBAUTH_LM_PWD_SZ); + (void) strncpy((char *)lm_pwd, password, SMBAUTH_LM_PWD_SZ); + smb_auth_lmupr(lm_pwd); + + return (smb_auth_DES(lm_hash, SMBAUTH_HASH_SZ, lm_pwd, + SMBAUTH_LM_PWD_SZ, (unsigned char *)SMBAUTH_LM_MAGIC_STR, + sizeof (SMBAUTH_LM_MAGIC_STR))); +} + +/* + * smb_auth_lm_response + * + * Create a LM response from the given LM hash and challenge. + * + * Returns SMBAUTH_FAILURE if any problems occur, SMBAUTH_SUCCESS if + * all goes well. + */ +static int +smb_auth_lm_response(unsigned char *hash, + unsigned char *challenge, int clen, + unsigned char *lm_rsp) +{ + unsigned char S21[21]; + + /* + * 14-byte LM Hash should be padded with 5 nul bytes to create + * a 21-byte string to be used in producing LM response + */ + bzero(&S21[SMBAUTH_HASH_SZ], 5); + bcopy(hash, S21, SMBAUTH_HASH_SZ); + + /* padded LM Hash -> LM Response */ + return (smb_auth_DES(lm_rsp, SMBAUTH_LM_RESP_SZ, S21, 21, + challenge, clen)); +} + +/* + * smb_auth_ntlm_hash + * + * Make NTLM Hash (using MD4) from the given password. + * The result will contain a 16-byte NTLM hash. + */ +int +smb_auth_ntlm_hash(char *password, unsigned char *hash) +{ + mts_wchar_t *unicode_password; + int length; + int rc; + + if (password == NULL || hash == NULL) + return (SMBAUTH_FAILURE); + + length = strlen(password); + unicode_password = (mts_wchar_t *) + malloc((length + 1) * sizeof (mts_wchar_t)); + + if (unicode_password == NULL) + return (SMBAUTH_FAILURE); + + length = smb_auth_qnd_unicode(unicode_password, password, length); + rc = smb_auth_md4(hash, (unsigned char *)unicode_password, length); + + free(unicode_password); + return (rc); +} + +/* + * smb_auth_ntlm_response + * + * Make LM/NTLM response from the given LM/NTLM Hash and given + * challenge. + */ +static int +smb_auth_ntlm_response(unsigned char *hash, + unsigned char *challenge, int clen, + unsigned char *ntlm_rsp) +{ + unsigned char S21[21]; + + bcopy(hash, S21, SMBAUTH_HASH_SZ); + bzero(&S21[SMBAUTH_HASH_SZ], 5); + if (smb_auth_DES((unsigned char *)ntlm_rsp, SMBAUTH_LM_RESP_SZ, + S21, 21, challenge, clen) == SMBAUTH_FAILURE) + return (0); + return (SMBAUTH_LM_RESP_SZ); +} + +/* + * smb_auth_gen_data_blob + * + * Fill the NTLMv2 data blob structure with information as described in + * "Implementing CIFS, The Common Internet File System". (pg. 282) + */ +static void +smb_auth_gen_data_blob(smb_auth_data_blob_t *blob, char *ntdomain) +{ + struct timeval now; + + (void) memset(blob->ndb_signature, 1, 2); + (void) memset(&blob->ndb_signature[2], 0, 2); + (void) memset(blob->ndb_reserved, 0, sizeof (blob->ndb_reserved)); + + (void) gettimeofday(&now, 0); + blob->ndb_timestamp = unix_micro_to_nt_time(&now); + randomize((char *)blob->ndb_clnt_challenge, + SMBAUTH_V2_CLNT_CHALLENGE_SZ); + (void) memset(blob->ndb_unknown, 0, sizeof (blob->ndb_unknown)); + blob->ndb_names[0].nne_len = smb_auth_qnd_unicode( + blob->ndb_names[0].nne_name, ntdomain, strlen(ntdomain)); + blob->ndb_names[0].nne_type = SMBAUTH_NAME_TYPE_DOMAIN_NETBIOS; + blob->ndb_names[1].nne_len = 0; + blob->ndb_names[1].nne_type = SMBAUTH_NAME_TYPE_LIST_END; + *blob->ndb_names[1].nne_name = 0; + (void) memset(blob->ndb_unknown2, 0, sizeof (blob->ndb_unknown2)); +} + +/* + * smb_auth_memcpy + * + * It increments the pointer to the destination buffer for the easy of + * concatenation. + */ +static void +smb_auth_memcpy(unsigned char **dstbuf, + unsigned char *srcbuf, + int srcbuf_len) +{ + (void) memcpy(*dstbuf, srcbuf, srcbuf_len); + *dstbuf += srcbuf_len; +} + +/* + * smb_auth_blob_to_string + * + * Prepare the data blob string which will be used in NTLMv2 response + * generation. + * + * Assumption: Caller must allocate big enough buffer to prevent buffer + * overrun. + * + * Returns the len of the data blob string. + */ +static int +smb_auth_blob_to_string(smb_auth_data_blob_t *blob, unsigned char *data_blob) +{ + unsigned char *bufp = data_blob; + + smb_auth_memcpy(&bufp, blob->ndb_signature, + sizeof (blob->ndb_signature)); + smb_auth_memcpy(&bufp, blob->ndb_reserved, + sizeof (blob->ndb_reserved)); + smb_auth_memcpy(&bufp, (unsigned char *)&blob->ndb_timestamp, + sizeof (blob->ndb_timestamp)); + smb_auth_memcpy(&bufp, blob->ndb_clnt_challenge, + SMBAUTH_V2_CLNT_CHALLENGE_SZ); + smb_auth_memcpy(&bufp, blob->ndb_unknown, sizeof (blob->ndb_unknown)); + smb_auth_memcpy(&bufp, (unsigned char *)&blob->ndb_names[0].nne_type, + sizeof (blob->ndb_names[0].nne_type)); + smb_auth_memcpy(&bufp, (unsigned char *)&blob->ndb_names[0].nne_len, + sizeof (blob->ndb_names[0].nne_len)); + smb_auth_memcpy(&bufp, (unsigned char *)blob->ndb_names[0].nne_name, + blob->ndb_names[0].nne_len); + smb_auth_memcpy(&bufp, (unsigned char *)&blob->ndb_names[1].nne_type, + sizeof (blob->ndb_names[1].nne_type)); + smb_auth_memcpy(&bufp, (unsigned char *)&blob->ndb_names[1].nne_len, + sizeof (blob->ndb_names[1].nne_len)); + smb_auth_memcpy(&bufp, blob->ndb_unknown2, sizeof (blob->ndb_unknown2)); + + /*LINTED E_PTRDIFF_OVERFLOW*/ + return (bufp - data_blob); +} + +/* + * smb_auth_ntlmv2_hash + * + * The NTLM v2 hash will be created from the given NTLM hash, username, + * and the NETBIOS name of the domain. + * + * The NTLMv2 hash will be returned via the ntlmv2_hash parameter which + * will be used in the calculation of the NTLMv2 and LMv2 responses. + */ +static int +smb_auth_ntlmv2_hash(unsigned char *ntlm_hash, + char *username, + char *ntdomain, + unsigned char *ntlmv2_hash) +{ + mts_wchar_t *data; + int data_len; + unsigned char *buf; + int rc; + + if (username == NULL || ntdomain == NULL) + return (SMBAUTH_FAILURE); + + (void) utf8_strupr(username); + (void) utf8_strupr(ntdomain); + + data_len = strlen(username) + strlen(ntdomain); + buf = (unsigned char *)malloc((data_len + 1) * sizeof (char)); + if (buf == NULL) + return (SMBAUTH_FAILURE); + + (void) snprintf((char *)buf, data_len + 1, "%s%s", username, ntdomain); + data = (mts_wchar_t *)malloc((data_len + 1) * sizeof (mts_wchar_t)); + if (data == NULL) { + free(buf); + return (SMBAUTH_FAILURE); + } + + data_len = smb_auth_qnd_unicode(data, (char *)buf, data_len); + rc = SMBAUTH_HMACT64((unsigned char *)data, data_len, ntlm_hash, + SMBAUTH_HASH_SZ, ntlmv2_hash); + + free(buf); + free(data); + return (rc); +} + +/* + * smb_auth_v2_response + * + * Caculates either the LMv2 or NTLMv2 response. + * + * Same algorithm is used for calculating both LMv2 or NTLMv2 responses. + * This routine will return NTLMv2 response if the data blob information + * is passed in as the clnt_data. Otherwise, it will return LMv2 response + * with the 8-byte client challenge(a.k.a blip) as the clnt_data. + * + * (LM/NTLM)v2 response is the hmac-md5 hash of the specified data + * (server challenge + NTLMv2 data blob or LMv2 client challenge) + * using the NTLMv2 hash as the key. + * + * Returns the size of the corresponding v2 response upon success. + * Otherwise, returns -1 on error. + */ +static int +smb_auth_v2_response( + unsigned char *hash, + unsigned char *srv_challenge, int slen, + unsigned char *clnt_data, int clen, + unsigned char *v2_rsp) +{ + unsigned char *hmac_data; + + hmac_data = (unsigned char *)malloc((slen + clen) * sizeof (char)); + if (!hmac_data) { + return (-1); + } + + (void) memcpy(hmac_data, srv_challenge, slen); + (void) memcpy(&hmac_data[slen], clnt_data, clen); + if (SMBAUTH_HMACT64(hmac_data, slen + clen, (unsigned char *)hash, + SMBAUTH_HASH_SZ, (unsigned char *)v2_rsp) != SMBAUTH_SUCCESS) + return (-1); + (void) memcpy(&v2_rsp[SMBAUTH_HASH_SZ], clnt_data, clen); + + free(hmac_data); + return (SMBAUTH_HASH_SZ + clen); +} + +/* + * smb_auth_set_info + * + * Fill the smb_auth_info instance with either NTLM or NTLMv2 related + * authentication information based on the LMCompatibilityLevel. + * + * If the LMCompatibilityLevel equals 2, the SMB Redirector will perform + * NTLM challenge/response authentication which requires the NTLM hash and + * NTLM response. + * + * If the LMCompatibilityLevel is 3 or above, the SMB Redirector will + * perfrom NTLMv2 challenge/response authenticatoin which requires the + * NTLM hash, NTLMv2 hash, NTLMv2 response and LMv2 response. + * + * Returns -1 on error. Otherwise, returns 0 upon success. + */ +int +smb_auth_set_info(char *username, + char *password, + unsigned char *ntlm_hash, + char *domain, + unsigned char *srv_challenge_key, + int srv_challenge_len, + int lmcomp_lvl, + smb_auth_info_t *auth) +{ + unsigned short blob_len; + unsigned char blob_buf[SMBAUTH_BLOB_MAXLEN]; + int rc; + + auth->lmcompatibility_lvl = lmcomp_lvl; + if (lmcomp_lvl == 2) { + auth->ci_len = 0; + *auth->ci = 0; + if (!ntlm_hash) { + if (smb_auth_ntlm_hash(password, auth->hash) != + SMBAUTH_SUCCESS) + return (-1); + } else { + (void) memcpy(auth->hash, ntlm_hash, SMBAUTH_HASH_SZ); + } + + auth->cs_len = smb_auth_ntlm_response(auth->hash, + srv_challenge_key, srv_challenge_len, auth->cs); + } else { + if (!ntlm_hash) { + if (smb_auth_ntlm_hash(password, auth->hash) != + SMBAUTH_SUCCESS) + return (-1); + } else { + (void) memcpy(auth->hash, ntlm_hash, SMBAUTH_HASH_SZ); + } + + if (smb_auth_ntlmv2_hash(auth->hash, username, + domain, auth->hash_v2) != SMBAUTH_SUCCESS) + return (-1); + + /* generate data blob */ + smb_auth_gen_data_blob(&auth->data_blob, domain); + blob_len = smb_auth_blob_to_string(&auth->data_blob, blob_buf); + + /* generate NTLMv2 response */ + rc = smb_auth_v2_response(auth->hash_v2, srv_challenge_key, + srv_challenge_len, blob_buf, blob_len, auth->cs); + + if (rc < 0) + return (-1); + + auth->cs_len = rc; + + /* generate LMv2 response */ + rc = smb_auth_v2_response(auth->hash_v2, srv_challenge_key, + srv_challenge_len, auth->data_blob.ndb_clnt_challenge, + SMBAUTH_V2_CLNT_CHALLENGE_SZ, auth->ci); + + if (rc < 0) + return (-1); + + auth->ci_len = rc; + } + + return (0); +} + +/* + * smb_auth_gen_session_key + * + * Generate the NTLM user session key if LMCompatibilityLevel is 2 or + * NTLMv2 user session key if LMCompatibilityLevel is 3 or above. + * + * NTLM_Session_Key = MD4(NTLM_Hash); + * + * NTLMv2_Session_Key = HMAC_MD5(NTLMv2Hash, 16, NTLMv2_HMAC, 16) + * + * Prior to calling this function, the auth instance should be set + * via smb_auth_set_info(). + * + * Returns the appropriate session key. + */ +int +smb_auth_gen_session_key(smb_auth_info_t *auth, unsigned char *session_key) +{ + int rc; + + if (auth->lmcompatibility_lvl == 2) + rc = smb_auth_md4(session_key, auth->hash, SMBAUTH_HASH_SZ); + else + rc = SMBAUTH_HMACT64((unsigned char *)auth->cs, + SMBAUTH_HASH_SZ, (unsigned char *)auth->hash_v2, + SMBAUTH_SESSION_KEY_SZ, session_key); + + return (rc); +} + +/* 100's of ns between 1/1/1970 and 1/1/1601 */ +#define NT_TIME_BIAS (134774LL * 24LL * 60LL * 60LL * 10000000LL) + +static uint64_t +unix_micro_to_nt_time(struct timeval *unix_time) +{ + uint64_t nt_time; + + nt_time = unix_time->tv_sec; + nt_time *= 10000000; /* seconds to 100ns */ + nt_time += unix_time->tv_usec * 10; + return (nt_time + NT_TIME_BIAS); +} + +static boolean_t +smb_lm_password_ok( + unsigned char *challenge, + uint32_t clen, + unsigned char *lm_hash, + unsigned char *passwd) +{ + unsigned char lm_resp[SMBAUTH_LM_RESP_SZ]; + int rc; + + rc = smb_auth_lm_response(lm_hash, challenge, clen, lm_resp); + if (rc != SMBAUTH_SUCCESS) + return (B_FALSE); + + return (bcmp(lm_resp, passwd, SMBAUTH_LM_RESP_SZ) == 0); +} + +static boolean_t +smb_ntlm_password_ok( + unsigned char *challenge, + uint32_t clen, + unsigned char *ntlm_hash, + unsigned char *passwd) +{ + unsigned char ntlm_resp[SMBAUTH_LM_RESP_SZ]; + int rc; + + rc = smb_auth_ntlm_response(ntlm_hash, challenge, clen, ntlm_resp); + + if (rc != SMBAUTH_LM_RESP_SZ) + return (B_FALSE); + + return (bcmp(ntlm_resp, passwd, SMBAUTH_LM_RESP_SZ) == 0); +} + +static boolean_t +smb_ntlmv2_password_ok( + unsigned char *challenge, + uint32_t clen, + unsigned char *ntlm_hash, + unsigned char *passwd, + int pwdlen, + char *username) +{ + unsigned char *clnt_blob; + int clnt_blob_len; + unsigned char ntlmv2_hash[SMBAUTH_HASH_SZ]; + unsigned char *ntlmv2_resp; + boolean_t ok; + + clnt_blob_len = pwdlen - SMBAUTH_HASH_SZ; + clnt_blob = &passwd[SMBAUTH_HASH_SZ]; + + /* + * 15.5.2 The NTLMv2 Password Hash, pg. 279, of the "Implementing CIFS" + * + * * The NTLMv2 Hash is created from: + * - NTLM hash + * - user's username, and + * - the name of the logon destination(i.e. the NetBIOS name of either + * the SMB server or NT Domain against which the suer is trying to + * authenticate. + * + * (N.L.) With my experience, this is not exactly true. It's really + * tricky how the NTLMv2 hash is generated by the Windows client when + * logging into a standalone server using NTLMv2 challenge / response. + * The NTLMv2 hash is actually created with the destination info="" + * as opposed to the SMB server name mentioned in the book. + */ + if (smb_auth_ntlmv2_hash(ntlm_hash, username, "", ntlmv2_hash) != + SMBAUTH_SUCCESS) { + return (B_FALSE); + } + + ntlmv2_resp = (unsigned char *)malloc(SMBAUTH_HASH_SZ + clnt_blob_len); + if (ntlmv2_resp == NULL) + return (B_FALSE); + + if (smb_auth_v2_response(ntlmv2_hash, challenge, + clen, clnt_blob, clnt_blob_len, ntlmv2_resp) < 0) { + free(ntlmv2_resp); + return (B_FALSE); + } + + ok = (bcmp(passwd, ntlmv2_resp, pwdlen) == 0); + + free(ntlmv2_resp); + return (ok); +} + +static int +smb_lmv2_password_ok( + unsigned char *challenge, + uint32_t clen, + unsigned char *ntlm_hash, + unsigned char *passwd, + char *username) +{ + unsigned char *clnt_challenge; + unsigned char ntlmv2_hash[SMBAUTH_HASH_SZ]; + unsigned char lmv2_resp[SMBAUTH_LM_RESP_SZ]; + + clnt_challenge = &passwd[SMBAUTH_HASH_SZ]; + + /* + * 15.5.2 The NTLMv2 Password Hash, pg. 279, of the "Implementing CIFS" + * + * The NTLMv2 Hash is created from: + * - NTLM hash + * - user's username, and + * - the name of the logon destination(i.e. the NetBIOS name of either + * the SMB server or NT Domain against which the suer is trying to + * authenticate. + * + * (N.L.) With my experience, this is not exactly true. It's really + * tricky how the NTLMv2 hash is generated by the Windows client when + * logging into a standalone server using LMv2 challenge/response. + * The NTLMv2 hash is actually created with the destination info = "" + * as opposed to the SMB server name mentioned in the book. + */ + if (smb_auth_ntlmv2_hash(ntlm_hash, username, "", ntlmv2_hash) != + SMBAUTH_SUCCESS) { + return (B_FALSE); + } + if (smb_auth_v2_response(ntlmv2_hash, challenge, + clen, clnt_challenge, SMBAUTH_V2_CLNT_CHALLENGE_SZ, + lmv2_resp) < 0) { + return (B_FALSE); + } + + return (bcmp(passwd, lmv2_resp, SMBAUTH_LM_RESP_SZ) == 0); +} + +/* + * smb_auth_validate_lm + * + * Validates given LM/LMv2 client response, passed in passwd arg, against + * stored user's password, passed in smbpw + * + * If LM level <=3 server accepts LM responses, otherwise LMv2 + */ +boolean_t +smb_auth_validate_lm( + unsigned char *challenge, + uint32_t clen, + smb_passwd_t *smbpw, + unsigned char *passwd, + int pwdlen, + char *username) +{ + int lmlevel; + boolean_t ok = B_FALSE; + + if (pwdlen != SMBAUTH_LM_RESP_SZ) + return (B_FALSE); + + smb_config_rdlock(); + lmlevel = smb_config_getnum(SMB_CI_LM_LEVEL); + smb_config_unlock(); + + if (lmlevel <= 3) { + ok = smb_lm_password_ok(challenge, clen, smbpw->pw_lmhash, + passwd); + } + + if (!ok) + ok = smb_lmv2_password_ok(challenge, clen, smbpw->pw_nthash, + passwd, username); + + return (ok); +} + +/* + * smb_auth_validate_nt + * + * Validates given NTLM/NTLMv2 client response, passed in passwd arg, against + * stored user's password, passed in smbpw + * + * If LM level <=4 server accepts NTLM/NTLMv2 responses, otherwise only NTLMv2 + */ +boolean_t +smb_auth_validate_nt( + unsigned char *challenge, + uint32_t clen, + smb_passwd_t *smbpw, + unsigned char *passwd, + int pwdlen, + char *username) +{ + int lmlevel; + boolean_t ok; + + smb_config_rdlock(); + lmlevel = smb_config_getnum(SMB_CI_LM_LEVEL); + smb_config_unlock(); + + if ((lmlevel == 5) && (pwdlen <= SMBAUTH_LM_RESP_SZ)) + return (B_FALSE); + + if (pwdlen > SMBAUTH_LM_RESP_SZ) + ok = smb_ntlmv2_password_ok(challenge, clen, + smbpw->pw_nthash, passwd, pwdlen, username); + else + ok = smb_ntlm_password_ok(challenge, clen, + smbpw->pw_nthash, passwd); + + return (ok); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_cfg.c b/usr/src/lib/smbsrv/libsmb/common/smb_cfg.c new file mode 100644 index 0000000000..844789e367 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_cfg.c @@ -0,0 +1,1090 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * CIFS configuration management library + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <synch.h> +#include <string.h> +#include <strings.h> +#include <syslog.h> +#include <netdb.h> +#include <ctype.h> +#include <sys/types.h> +#include <libscf.h> +#include <smbsrv/libsmb.h> + +typedef struct smb_cfg_param { + char *sc_pg; + char *sc_name; + int sc_type; + char *sc_value; + uint32_t sc_flags; +} smb_cfg_param_t; + +/* + * config parameter flags + */ +#define SMB_CF_NOTINIT 0x00 /* Not initialized yet */ +#define SMB_CF_DEFINED 0x01 /* Defined/read from env */ +#define SMB_CF_MODIFIED 0x02 /* Has been modified */ +#define SMB_CF_SYSTEM 0x04 /* system; not part of cifs config */ + +#define SMB_CL_NONE 0 +#define SMB_CL_READ 1 +#define SMB_CL_WRITE 2 + +/* idmap SMF fmri and Property Group */ +#define IDMAP_FMRI_PREFIX "system/idmap" +#define MACHINE_SID "machine_sid" +#define MAPPING_DOMAIN "mapping_domain" +#define GLOBAL_CATALOG "global_catalog" +#define IDMAP_PG_NAME "config" + +#define SMB_SECMODE_WORKGRP_STR "workgroup" +#define SMB_SECMODE_DOMAIN_STR "domain" + +#define SMB_ENC_LEN 1024 +#define SMB_DEC_LEN 256 + +static char *b64_data = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static rwlock_t smb_cfg_rwlk; +static int lock_type = SMB_CL_NONE; + +/* + * IMPORTANT: any changes to the order of this table's entries + * need to be reflected in smb_cfg_id_t enum in libsmb.h + */ +static smb_cfg_param_t smb_cfg_table[] = +{ + /* Redirector configuration, User space */ + {SMBD_PG_NAME, SMB_CD_RDR_IPCMODE, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PROTECTED_PG_NAME, SMB_CD_RDR_IPCUSER, + SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PROTECTED_PG_NAME, SMB_CD_RDR_IPCPWD, + SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + + /* Oplock configuration, Kernel Only */ + {SMBD_PG_NAME, SMB_CD_OPLOCK_ENABLE, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_OPLOCK_TIMEOUT, + SCF_TYPE_INTEGER, 0, SMB_CF_NOTINIT}, + + /* Autohome configuration */ + {SMBD_PG_NAME, SMB_CD_AUTOHOME_MAP, + SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + + /* Domain/PDC configuration */ + {SMBD_PG_NAME, SMB_CD_DOMAIN_SID, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_DOMAIN_MEMB, SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_DOMAIN_NAME, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_DOMAIN_SRV, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + + /* WINS configuration */ + {SMBD_PG_NAME, SMB_CD_WINS_SRV1, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_WINS_SRV2, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_WINS_EXCL, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + + /* RPC services configuration */ + {SMBD_PG_NAME, SMB_CD_SRVSVC_SHRSET_ENABLE, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_LOGR_ENABLE, SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_MLRPC_KALIVE, + SCF_TYPE_INTEGER, 0, SMB_CF_NOTINIT}, + + /* Kmod specific configuration */ + {SMBD_PG_NAME, SMB_CD_MAX_BUFSIZE, SCF_TYPE_INTEGER, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_MAX_WORKERS, SCF_TYPE_INTEGER, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_MAX_CONNECTIONS, + SCF_TYPE_INTEGER, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_KEEPALIVE, SCF_TYPE_INTEGER, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_RESTRICT_ANON, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + + {SMBD_PG_NAME, SMB_CD_SIGNING_ENABLE, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_SIGNING_REQD, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_SIGNING_CHECK, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + + /* Kmod tuning configuration */ + {SMBD_PG_NAME, SMB_CD_FLUSH_REQUIRED, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_SYNC_ENABLE, SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_DIRSYMLINK_DISABLE, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_ANNONCE_QUOTA, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + + /* SMBd configuration */ + {SMBD_PG_NAME, SMB_CD_SECURITY, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_NBSCOPE, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_SYS_CMNT, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_LM_LEVEL, SCF_TYPE_INTEGER, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_MSDCS_DISABLE, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + + /* ADS Configuration */ + {SMBD_PG_NAME, SMB_CD_ADS_ENABLE, SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PROTECTED_PG_NAME, SMB_CD_ADS_USER, + SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PROTECTED_PG_NAME, SMB_CD_ADS_PASSWD, + SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_ADS_DOMAIN, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_ADS_USER_CONTAINER, + SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_ADS_SITE, SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_ADS_IPLOOKUP, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + + /* Dynamic DNS */ + {SMBD_PG_NAME, SMB_CD_DYNDNS_ENABLE, + SCF_TYPE_BOOLEAN, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_DYNDNS_RETRY_COUNT, + SCF_TYPE_INTEGER, 0, SMB_CF_NOTINIT}, + {SMBD_PG_NAME, SMB_CD_DYNDNS_RETRY_SEC, + SCF_TYPE_INTEGER, 0, SMB_CF_NOTINIT}, + + {SMBD_PROTECTED_PG_NAME, SMB_CD_MACHINE_PASSWD, + SCF_TYPE_ASTRING, 0, SMB_CF_NOTINIT} + /* SMB_CI_MAX */ +}; + +static boolean_t smb_is_base64(unsigned char c); +static char *smb_base64_encode(char *str_to_encode); +static char *smb_base64_decode(char *encoded_str); +static int smb_config_update(smb_cfg_param_t *cfg, char *value); +static int smb_config_save_all(); +static int smb_config_save(char *pgname); + +static boolean_t +smb_is_base64(unsigned char c) +{ + return (isalnum(c) || (c == '+') || (c == '/')); +} + +/* + * smb_base64_encode + * + * Encode a string using base64 algorithm. + * Caller should free the returned buffer when done. + */ +static char * +smb_base64_encode(char *str_to_encode) +{ + int ret_cnt = 0; + int i = 0, j = 0; + char arr_3[3], arr_4[4]; + int len = strlen(str_to_encode); + char *ret = malloc(SMB_ENC_LEN); + + if (ret == NULL) { + return (NULL); + } + + while (len--) { + arr_3[i++] = *(str_to_encode++); + if (i == 3) { + arr_4[0] = (arr_3[0] & 0xfc) >> 2; + arr_4[1] = ((arr_3[0] & 0x03) << 4) + + ((arr_3[1] & 0xf0) >> 4); + arr_4[2] = ((arr_3[1] & 0x0f) << 2) + + ((arr_3[2] & 0xc0) >> 6); + arr_4[3] = arr_3[2] & 0x3f; + + for (i = 0; i < 4; i++) + ret[ret_cnt++] = b64_data[arr_4[i]]; + i = 0; + } + } + + if (i) { + for (j = i; j < 3; j++) + arr_3[j] = '\0'; + + arr_4[0] = (arr_3[0] & 0xfc) >> 2; + arr_4[1] = ((arr_3[0] & 0x03) << 4) + + ((arr_3[1] & 0xf0) >> 4); + arr_4[2] = ((arr_3[1] & 0x0f) << 2) + + ((arr_3[2] & 0xc0) >> 6); + arr_4[3] = arr_3[2] & 0x3f; + + for (j = 0; j < (i + 1); j++) + ret[ret_cnt++] = b64_data[arr_4[j]]; + + while (i++ < 3) + ret[ret_cnt++] = '='; + } + + ret[ret_cnt++] = '\0'; + return (ret); +} + +/* + * smb_base64_decode + * + * Decode using base64 algorithm. + * Caller should free the returned buffer when done. + */ +static char * +smb_base64_decode(char *encoded_str) +{ + int len = strlen(encoded_str); + int i = 0, j = 0; + int en_ind = 0; + char arr_4[4], arr_3[3]; + int ret_cnt = 0; + char *ret = malloc(SMB_DEC_LEN); + char *p; + + if (ret == NULL) { + return (NULL); + } + + while (len-- && (encoded_str[en_ind] != '=') && + smb_is_base64(encoded_str[en_ind])) { + arr_4[i++] = encoded_str[en_ind]; + en_ind++; + if (i == 4) { + for (i = 0; i < 4; i++) { + if ((p = strchr(b64_data, arr_4[i])) == NULL) + return (NULL); + + arr_4[i] = (int)(p - b64_data); + } + + arr_3[0] = (arr_4[0] << 2) + + ((arr_4[1] & 0x30) >> 4); + arr_3[1] = ((arr_4[1] & 0xf) << 4) + + ((arr_4[2] & 0x3c) >> 2); + arr_3[2] = ((arr_4[2] & 0x3) << 6) + + arr_4[3]; + + for (i = 0; i < 3; i++) + ret[ret_cnt++] = arr_3[i]; + + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) + arr_4[j] = 0; + + for (j = 0; j < 4; j++) { + if ((p = strchr(b64_data, arr_4[j])) == NULL) + return (NULL); + + arr_4[j] = (int)(p - b64_data); + } + arr_3[0] = (arr_4[0] << 2) + + ((arr_4[1] & 0x30) >> 4); + arr_3[1] = ((arr_4[1] & 0xf) << 4) + + ((arr_4[2] & 0x3c) >> 2); + arr_3[2] = ((arr_4[2] & 0x3) << 6) + + arr_4[3]; + for (j = 0; j < (i - 1); j++) + ret[ret_cnt++] = arr_3[j]; + } + + ret[ret_cnt++] = '\0'; + return (ret); +} + +/* + * Basically commit the transaction. + */ +static int +smb_config_saveenv(smb_scfhandle_t *handle) +{ + int ret = 0; + + ret = smb_smf_end_transaction(handle); + + smb_smf_scf_fini(handle); + return (ret); +} + +/* + * smb_config_getenv + * + * Get the property value from SMF. + */ +char * +smb_config_getenv(smb_cfg_id_t id) +{ + smb_scfhandle_t *handle; + char *value; + + if ((value = malloc(MAX_VALUE_BUFLEN * sizeof (char))) == NULL) + return (NULL); + + handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); + if (handle == NULL) { + free(value); + return (NULL); + } + + (void) smb_smf_create_service_pgroup(handle, smb_cfg_table[id].sc_pg); + + if (smb_smf_get_property(handle, smb_cfg_table[id].sc_type, + smb_cfg_table[id].sc_name, value, + sizeof (char) * MAX_VALUE_BUFLEN) != 0) { + smb_smf_scf_fini(handle); + free(value); + return (NULL); + } + + smb_smf_scf_fini(handle); + return (value); +} + +/* + * smb_config_getenv_dec + * + * For protected property, the value obtained from SMF will be decoded. + * The decoded property value will be returned. + * + * This function should only be called by smb_config_load to populate + * the SMB config cache. + */ +static char * +smb_config_getenv_dec(smb_cfg_id_t id) +{ + smb_scfhandle_t *handle; + char *value; + char *dec; + + if ((value = malloc(MAX_VALUE_BUFLEN * sizeof (char))) == NULL) + return (NULL); + + handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); + if (handle == NULL) { + free(value); + return (NULL); + } + + (void) smb_smf_create_service_pgroup(handle, smb_cfg_table[id].sc_pg); + + if (smb_smf_get_property(handle, smb_cfg_table[id].sc_type, + smb_cfg_table[id].sc_name, value, + sizeof (char) * MAX_VALUE_BUFLEN) != 0) { + smb_smf_scf_fini(handle); + free(value); + return (NULL); + } + smb_smf_scf_fini(handle); + if (strcmp(smb_cfg_table[id].sc_pg, SMBD_PROTECTED_PG_NAME)) + return (value); + + if (!value) + return (NULL); + + if (*value == '\0') { + free(value); + return (NULL); + } + + dec = smb_base64_decode(value); + free(value); + return (dec); +} + +static char * +smb_config_getenv_generic(char *name, char *svc_fmri_prefix, char *svc_propgrp) +{ + smb_scfhandle_t *handle; + char *value; + + if ((value = malloc(MAX_VALUE_BUFLEN * sizeof (char))) == NULL) + return (NULL); + + handle = smb_smf_scf_init(svc_fmri_prefix); + if (handle == NULL) { + free(value); + return (NULL); + } + + (void) smb_smf_create_service_pgroup(handle, svc_propgrp); + + if (smb_smf_get_string_property(handle, name, value, + sizeof (char) * MAX_VALUE_BUFLEN) != 0) { + smb_smf_scf_fini(handle); + free(value); + return (NULL); + } + + smb_smf_scf_fini(handle); + return (value); + +} + +int +smb_config_setenv_generic(char *svc_fmri_prefix, char *svc_propgrp, + char *name, char *value) +{ + smb_scfhandle_t *handle = NULL; + int rc = 0; + + + handle = smb_smf_scf_init(svc_fmri_prefix); + if (handle == NULL) { + return (1); + } + + (void) smb_smf_create_service_pgroup(handle, svc_propgrp); + + if (smb_smf_start_transaction(handle) != SMBD_SMF_OK) { + smb_smf_scf_fini(handle); + return (1); + } + + if (smb_smf_set_string_property(handle, name, value) != SMBD_SMF_OK) + rc = 1; + + if (smb_smf_end_transaction(handle) != SMBD_SMF_OK) + rc = 1; + + smb_smf_scf_fini(handle); + return (rc); +} + +/* + * smb_config_setenv + * + * For protected properties, the value will be encoded using base64 + * algorithm. The encoded string will be stored in SMF. + */ +int +smb_config_setenv(smb_cfg_id_t id, char *value) +{ + smb_scfhandle_t *handle = NULL; + char *enc = NULL; + int is_protected = 0; + + if ((id >= SMB_CI_MAX) || (id < 0)) { + return (1); + } + + handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); + if (handle == NULL) { + return (1); + } + + (void) smb_smf_create_service_pgroup(handle, smb_cfg_table[id].sc_pg); + + if (smb_smf_start_transaction(handle) != SMBD_SMF_OK) { + smb_smf_scf_fini(handle); + return (1); + } + + if (strcmp(smb_cfg_table[id].sc_pg, SMBD_PROTECTED_PG_NAME) == 0) { + if ((value == NULL) || (*value == '\0')) { + (void) smb_smf_end_transaction(handle); + smb_smf_scf_fini(handle); + return (1); + } + + if ((enc = smb_base64_encode(value)) == NULL) { + (void) smb_smf_end_transaction(handle); + smb_smf_scf_fini(handle); + return (1); + } + + is_protected = 1; + } + + if (smb_smf_set_property(handle, smb_cfg_table[id].sc_type, + smb_cfg_table[id].sc_name, is_protected ? enc : value) + != SMBD_SMF_OK) { + if (enc) + free(enc); + (void) smb_smf_end_transaction(handle); + smb_smf_scf_fini(handle); + return (1); + } + + if (enc) + free(enc); + + if (smb_smf_end_transaction(handle) != SMBD_SMF_OK) { + smb_smf_scf_fini(handle); + return (1); + } + + smb_smf_scf_fini(handle); + return (0); +} + +static void +smb_config_setenv_trans(smb_scfhandle_t *handle, int type, + char *name, char *value) +{ + if (smb_smf_set_property(handle, type, name, value) != SMBD_SMF_OK) { + syslog(LOG_ERR, "Failed to save service property %s", name); + } +} + +/* + * smb_config_setenv_trans_protected + * + * This function should only be called to set protected properties + * in SMF. The argument 'value' will be encoded using base64 algorithm. + * The encoded string will be stored in SMF. + */ +static void +smb_config_setenv_trans_protected(smb_scfhandle_t *handle, char *name, + char *value) +{ + char *enc; + + if ((value == NULL) || (*value == '\0')) + return; + + if ((enc = smb_base64_encode(value)) == NULL) + return; + + if (smb_smf_set_string_property(handle, name, enc) != SMBD_SMF_OK) { + syslog(LOG_ERR, "Failed to save service protected property" + " %s", name); + } + + free(enc); +} + +int +smb_config_unsetenv(smb_cfg_id_t id) +{ + smb_scfhandle_t *handle = NULL; + int ret = 1; + + handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); + if (handle == NULL) { + return (ret); + } + + (void) smb_smf_create_service_pgroup(handle, smb_cfg_table[id].sc_pg); + if (smb_smf_start_transaction(handle) != SMBD_SMF_OK) { + smb_smf_scf_fini(handle); + return (ret); + } + ret = smb_smf_delete_property(handle, smb_cfg_table[id].sc_name); + (void) smb_smf_end_transaction(handle); + + smb_smf_scf_fini(handle); + return (ret); +} + +static int +smb_config_unsetenv_trans(smb_scfhandle_t *handle, char *name) +{ + return (smb_smf_delete_property(handle, name)); +} + +/* + * smb_config_load + * + * Loads all the CIFS configuration parameters and sets up the + * config table. + */ +int +smb_config_load() +{ + smb_cfg_id_t id; + smb_cfg_param_t *cfg; + char *value; + + (void) rw_rdlock(&smb_cfg_rwlk); + for (id = 0; id < SMB_CI_MAX; id++) { + value = smb_config_getenv_dec(id); + cfg = &smb_cfg_table[id]; + /* + * enval == 0 could mean two things, either the + * config param is not defined, or it has been + * removed. If the variable has already been defined + * and now enval is 0, it should be removed, otherwise + * we don't need to do anything in this case. + */ + if ((cfg->sc_flags & SMB_CF_DEFINED) || value) { + if (smb_config_update(cfg, value) != 0) { + (void) rw_unlock(&smb_cfg_rwlk); + if (value) + free(value); + return (1); + } + } + if (value) { + free(value); + } + } + + (void) rw_unlock(&smb_cfg_rwlk); + + return (0); +} + +/* + * smb_config_get + * + * Returns value of the specified config param. + * The return value is a string pointer to the locally + * allocated memory if the config param is defined + * otherwise it would be NULL. + * + * This function MUST be called after a smb_config_rd/wrlock + * function. Caller MUST NOT modify the returned buffer directly. + */ +char * +smb_config_get(smb_cfg_id_t id) +{ + if (id < SMB_CI_MAX) + return (smb_cfg_table[id].sc_value); + + return (0); +} + +/* + * smb_config_getstr + * + * Returns value of the specified config param. + * The returned pointer never will be NULL if the given + * 'id' is valid. If the config param is not defined its + * default value will be returned. + * + * This function MUST be called after a smb_config_rd/wrlock + * function. Caller MUST NOT modify the returned buffer directly. + */ +char * +smb_config_getstr(smb_cfg_id_t id) +{ + smb_cfg_param_t *cfg; + + if (id < SMB_CI_MAX) { + cfg = &smb_cfg_table[id]; + if (cfg->sc_value) + return (cfg->sc_value); + } + + return (NULL); +} + +/* + * smb_config_getnum + * + * Returns the value of a numeric config param. + * If the config param is not defined it'll return the + * default value. + * + * This function MUST be called after a smb_config_rd/wrlock + * function. + */ +uint32_t +smb_config_getnum(smb_cfg_id_t id) +{ + smb_cfg_param_t *cfg; + char *strval = NULL; + + if (id < SMB_CI_MAX) { + cfg = &smb_cfg_table[id]; + if (cfg->sc_value) + strval = cfg->sc_value; + + if (strval) + return (strtol(strval, 0, 10)); + } + + return (0); +} + +/* + * smb_config_getyorn + * + * Returns the value of a yes/no config param. + * Returns 1 is config is set to "yes", otherwise 0. + * + * This function MUST be called after a smb_config_rd/wrlock + * function. + */ +int +smb_config_getyorn(smb_cfg_id_t id) +{ + char *val; + + val = smb_config_get(id); + if (val) { + if (strcasecmp(val, "true") == 0) + return (1); + } + + return (0); +} + +/* + * smb_config_set + * + * Set/update the specified config param with the given + * value. If the value is NULL the config param will be + * unset as if it is not defined. + * + * This function MUST be called after a smb_config_wrlock + * function. + */ +int +smb_config_set(smb_cfg_id_t id, char *value) +{ + smb_cfg_param_t *cfg; + int rc = 0; + + if (id < SMB_CI_MAX) { + cfg = &smb_cfg_table[id]; + rc = smb_config_update(cfg, value); + if (rc == 0) + cfg->sc_flags |= SMB_CF_MODIFIED; + return (rc); + } + + return (1); +} + +/* + * smb_config_setnum + * + * Set/update the specified config param with the given + * value. This is used for numeric config params. The given + * number will be converted to string before setting the + * config param. + * + * This function MUST be called after a smb_config_wrlock + * function. + */ +int +smb_config_setnum(smb_cfg_id_t id, uint32_t num) +{ + smb_cfg_param_t *cfg; + char value[32]; + int rc = 0; + + if (id < SMB_CI_MAX) { + cfg = &smb_cfg_table[id]; + (void) snprintf(value, sizeof (value), "%u", num); + rc = smb_config_update(cfg, value); + if (rc == 0) + cfg->sc_flags |= SMB_CF_MODIFIED; + return (rc); + } + + return (1); +} + +/* + * smb_config_rdlock + * + * Lock the config table for read access. + * This function MUST be called before any kind of + * read access to the config table i.e. all flavors of + * smb_config_get function + */ +void +smb_config_rdlock() +{ + (void) rw_rdlock(&smb_cfg_rwlk); + lock_type = SMB_CL_READ; +} + +/* + * smb_config_wrlock + * + * Lock the config table for write access. + * This function MUST be called before any kind of + * write access to the config table i.e. all flavors of + * smb_config_set function + */ +void +smb_config_wrlock() +{ + (void) rw_wrlock(&smb_cfg_rwlk); + lock_type = SMB_CL_WRITE; +} + +/* + * smb_config_wrlock + * + * Unlock the config table. + * If the config table has been locked for write access + * smb_config_save_all() will be called to save the changes + * before unlocking the table. + * + * This function MUST be called after smb_config_rd/wrlock + */ +void +smb_config_unlock() +{ + if (lock_type == SMB_CL_WRITE) + (void) smb_config_save_all(); + (void) rw_unlock(&smb_cfg_rwlk); +} + +/* + * smb_config_save_all + * + * Save all modified parameters to SMF. + */ +static int +smb_config_save_all() +{ + int rc; + + if ((rc = smb_config_save(SMBD_PG_NAME)) != 0) + return (rc); + + return (smb_config_save(SMBD_PROTECTED_PG_NAME)); +} + +/* + * smb_config_save + * + * Scan the config table and call smb_config_setenv/smb_config_unsetenv + * for params in the specified property group that has been modified. + * When the scan is finished, smb_config_saveenv() will be called to + * make the changes persistent. + */ +static int +smb_config_save(char *pgname) +{ + smb_cfg_id_t id; + smb_cfg_param_t *cfg; + smb_scfhandle_t *handle = NULL; + int dorefresh = 0; + + handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); + if (handle == NULL) { + syslog(LOG_ERR, "smbd: cannot save configuration"); + return (1); + } + + (void) smb_smf_create_service_pgroup(handle, pgname); + if (smb_smf_start_transaction(handle) != SMBD_SMF_OK) { + syslog(LOG_ERR, "smbd: cannot save configuration"); + return (1); + } + + for (id = 0; id < SMB_CI_MAX; id++) { + cfg = &smb_cfg_table[id]; + if (strcmp(cfg->sc_pg, pgname)) + continue; + + if (cfg->sc_flags & SMB_CF_MODIFIED) { + if (cfg->sc_value) { + if (strcmp(pgname, SMBD_PG_NAME) == 0) + smb_config_setenv_trans(handle, + cfg->sc_type, cfg->sc_name, + cfg->sc_value); + else + smb_config_setenv_trans_protected( + handle, cfg->sc_name, + cfg->sc_value); + } else { + (void) smb_config_unsetenv_trans(handle, + cfg->sc_name); + } + cfg->sc_flags &= ~SMB_CF_MODIFIED; + dorefresh = 1; + } + } + + if (smb_config_saveenv(handle) != 0) { + syslog(LOG_ERR, "smbd: cannot save configuration"); + return (1); + } + if (dorefresh) + (void) smf_refresh_instance(SMBD_DEFAULT_INSTANCE_FMRI); + return (0); +} + +/* + * smb_config_update + * + * Updates the specified config param with the given value. + * This function is called both on (re)load and set. + */ +static int +smb_config_update(smb_cfg_param_t *cfg, char *value) +{ + char *curval; + int rc = 0; + int len; + + if (value) { + len = strlen(value); + if (cfg->sc_value) { + curval = (char *)realloc(cfg->sc_value, + (len + 1)); + } else { + curval = (char *)malloc(len + 1); + } + + if (curval) { + cfg->sc_value = curval; + (void) strcpy(cfg->sc_value, value); + cfg->sc_flags |= SMB_CF_DEFINED; + } else { + rc = 1; + } + } else if (cfg->sc_value) { + free(cfg->sc_value); + cfg->sc_value = NULL; + cfg->sc_flags &= ~SMB_CF_DEFINED; + } + + return (rc); +} + +uint8_t +smb_config_get_fg_flag() +{ + uint8_t run_fg = 0; /* Default is to run in daemon mode */ + smb_scfhandle_t *handle = NULL; + + handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); + if (handle == NULL) { + return (run_fg); + } + + if (smb_smf_create_service_pgroup(handle, + SMBD_PG_NAME) != SMBD_SMF_OK) { + smb_smf_scf_fini(handle); + return (run_fg); + } + + if (smb_smf_get_boolean_property(handle, "run_fg", &run_fg) != 0) { + smb_smf_scf_fini(handle); + return (run_fg); + } + + smb_smf_scf_fini(handle); + + return (run_fg); +} + +/* + * smb_config_get_localsid + * + * Returns value of the "config/machine_sid" parameter + * from the IDMAP SMF configuration repository. + * + */ +char * +smb_config_get_localsid(void) +{ + return (smb_config_getenv_generic(MACHINE_SID, IDMAP_FMRI_PREFIX, + IDMAP_PG_NAME)); +} + +/* + * smb_config_set_idmap_domain + * + * Set the "config/mapping_domain" parameter from IDMAP SMF repository. + */ +int +smb_config_set_idmap_domain(char *value) +{ + return (smb_config_setenv_generic(IDMAP_FMRI_PREFIX, IDMAP_PG_NAME, + MAPPING_DOMAIN, value)); +} + +/* + * smb_config_set_idmap_gc + * + * Set the "config/global_catalog" parameter from IDMAP SMF repository. + */ +int +smb_config_set_idmap_gc(char *value) +{ + return (smb_config_setenv_generic(IDMAP_FMRI_PREFIX, IDMAP_PG_NAME, + GLOBAL_CATALOG, value)); +} + +/* + * smb_config_refresh_idmap + * + * Refresh IDMAP SMF service after making changes to its configuration. + */ +int +smb_config_refresh_idmap(void) +{ + char instance[32]; + + (void) snprintf(instance, sizeof (instance), "%s:default", + IDMAP_FMRI_PREFIX); + return (smf_refresh_instance(instance)); +} + +int +smb_config_secmode_fromstr(char *secmode) +{ + if (secmode == NULL) + return (SMB_SECMODE_WORKGRP); + + if (strcasecmp(secmode, SMB_SECMODE_DOMAIN_STR) == 0) + return (SMB_SECMODE_DOMAIN); + + return (SMB_SECMODE_WORKGRP); +} + +char * +smb_config_secmode_tostr(int secmode) +{ + if (secmode == SMB_SECMODE_DOMAIN) + return (SMB_SECMODE_DOMAIN_STR); + + return (SMB_SECMODE_WORKGRP_STR); +} + +int +smb_config_get_secmode() +{ + char *p; + + p = smb_config_getstr(SMB_CI_SECURITY); + return (smb_config_secmode_fromstr(p)); +} + +int +smb_config_set_secmode(int secmode) +{ + char *p; + + p = smb_config_secmode_tostr(secmode); + return (smb_config_set(SMB_CI_SECURITY, p)); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_crypt.c b/usr/src/lib/smbsrv/libsmb/common/smb_crypt.c new file mode 100644 index 0000000000..94489b0b40 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_crypt.c @@ -0,0 +1,204 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/md4.h> +#include <sys/types.h> +#include <string.h> +#include <security/cryptoki.h> +#include <security/pkcs11.h> +#include <cryptoutil.h> +#include <smbsrv/libsmb.h> + +static void smb_auth_keyprep(const unsigned char *pwstr, unsigned char *keystr); + +/* + * smb_auth_md4 + * + * Compute an MD4 digest. + */ +int +smb_auth_md4(unsigned char *result, unsigned char *input, int length) +{ + MD4_CTX md4_context; + + MD4Init(&md4_context); + MD4Update(&md4_context, input, length); + MD4Final(result, &md4_context); + return (SMBAUTH_SUCCESS); +} + +int +smb_auth_hmac_md5(unsigned char *data, + int data_len, + unsigned char *key, + int key_len, + unsigned char *digest) +{ + CK_RV rv; + CK_MECHANISM mechanism; + CK_OBJECT_HANDLE hKey; + CK_SESSION_HANDLE hSession; + unsigned long diglen = MD_DIGEST_LEN; + + mechanism.mechanism = CKM_MD5_HMAC; + mechanism.pParameter = 0; + mechanism.ulParameterLen = 0; + rv = SUNW_C_GetMechSession(mechanism.mechanism, &hSession); + if (rv != CKR_OK) { + return (SMBAUTH_FAILURE); + } + + rv = SUNW_C_KeyToObject(hSession, mechanism.mechanism, + key, key_len, &hKey); + if (rv != CKR_OK) { + (void) C_CloseSession(hSession); + return (SMBAUTH_FAILURE); + } + + /* Initialize the digest operation in the session */ + rv = C_SignInit(hSession, &mechanism, hKey); + if (rv != CKR_OK) { + (void) C_DestroyObject(hSession, hKey); + (void) C_CloseSession(hSession); + return (SMBAUTH_FAILURE); + } + rv = C_SignUpdate(hSession, (CK_BYTE_PTR)data, data_len); + if (rv != CKR_OK) { + (void) C_DestroyObject(hSession, hKey); + (void) C_CloseSession(hSession); + return (SMBAUTH_FAILURE); + } + rv = C_SignFinal(hSession, (CK_BYTE_PTR)digest, &diglen); + if (rv != CKR_OK) { + (void) C_DestroyObject(hSession, hKey); + (void) C_CloseSession(hSession); + return (SMBAUTH_FAILURE); + } + (void) C_DestroyObject(hSession, hKey); + (void) C_CloseSession(hSession); + if (diglen != MD_DIGEST_LEN) { + return (SMBAUTH_FAILURE); + } + return (SMBAUTH_SUCCESS); +} + +int +smb_auth_DES(unsigned char *Result, int ResultLen, + unsigned char *Key, int KeyLen, + unsigned char *Data, int DataLen) +{ + CK_RV rv; + CK_MECHANISM mechanism; + CK_OBJECT_HANDLE hKey; + CK_SESSION_HANDLE hSession; + CK_ULONG ciphertext_len; + uchar_t des_key[8]; + int error = 0; + int K, D; + int k, d; + + /* Calculate proper number of iterations */ + K = KeyLen / 7; + D = DataLen / 8; + + if (ResultLen < (K * 8 * D)) { + return (SMBAUTH_FAILURE); + } + + /* + * Use SUNW convenience function to initialize the cryptoki + * library, and open a session with a slot that supports + * the mechanism we plan on using. + */ + mechanism.mechanism = CKM_DES_ECB; + mechanism.pParameter = NULL; + mechanism.ulParameterLen = 0; + rv = SUNW_C_GetMechSession(mechanism.mechanism, &hSession); + if (rv != CKR_OK) { + return (SMBAUTH_FAILURE); + } + + for (k = 0; k < K; k++) { + smb_auth_keyprep(&Key[k * 7], des_key); + rv = SUNW_C_KeyToObject(hSession, mechanism.mechanism, + des_key, 8, &hKey); + if (rv != CKR_OK) { + error = 1; + goto exit_session; + } + /* Initialize the encryption operation in the session */ + rv = C_EncryptInit(hSession, &mechanism, hKey); + if (rv != CKR_OK) { + error = 1; + goto exit_encrypt; + } + ciphertext_len = DataLen; + for (d = 0; d < D; d++) { + /* Read in the data and encrypt this portion */ + rv = C_EncryptUpdate(hSession, + (CK_BYTE_PTR)Data + (d * 8), 8, + &Result[(k * (8 * D)) + (d * 8)], + &ciphertext_len); + if (rv != CKR_OK) { + error = 1; + goto exit_encrypt; + } + } + (void) C_DestroyObject(hSession, hKey); + } + goto exit_session; + +exit_encrypt: + (void) C_DestroyObject(hSession, hKey); +exit_session: + (void) C_CloseSession(hSession); + + if (error) + return (SMBAUTH_FAILURE); + + return (SMBAUTH_SUCCESS); +} + +/* + * smb_auth_keyprep + * + * Takes 7 bytes of keying material and expands it into 8 bytes with + * the keying material in the upper 7 bits of each byte. + */ +static void +smb_auth_keyprep(const unsigned char *pwstr, unsigned char *keystr) +{ + keystr[0] = pwstr[0]; + keystr[1] = (pwstr[0] << 7) | (pwstr[1] >> 1); + keystr[2] = (pwstr[1] << 6) | (pwstr[2] >> 2); + keystr[3] = (pwstr[2] << 5) | (pwstr[3] >> 3); + keystr[4] = (pwstr[3] << 4) | (pwstr[4] >> 4); + keystr[5] = (pwstr[4] << 3) | (pwstr[5] >> 5); + keystr[6] = (pwstr[5] << 2) | (pwstr[6] >> 6); + keystr[7] = pwstr[6] << 1; +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_ctxbuf.c b/usr/src/lib/smbsrv/libsmb/common/smb_ctxbuf.c new file mode 100644 index 0000000000..2b51f2b6af --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_ctxbuf.c @@ -0,0 +1,120 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Buffer manipulation routines. These routines can be used to format + * data within a data buffer without worrying about overrunning the + * buffer. + * + * A ctxbuf_t structure is used to track the current location within + * the buffer. The ctxbuf_init() must be called first to initialize the + * context structure. ctxbuf_printf() can then be called to fill the buffer. + * ctxbuf_printf will discard any data that would overrun the buffer and + * the buffer will always be null terminated. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdarg.h> +#include <smbsrv/libsmb.h> + +/* + * smb_ctxbuf_init + * + * Initialize the buffer context structure. + * This must be called before any of the other + * buffer routines can be used. + * + * Returns -1 if invalid parameters, 0 otherwise + */ +int +smb_ctxbuf_init(smb_ctxbuf_t *ctx, unsigned char *buf, size_t buflen) +{ + if (ctx == 0 || buf == 0 || buflen == 0) + return (-1); + + buf[0] = '\0'; + + ctx->basep = buf; + ctx->curp = buf; + ctx->endp = &buf[buflen]; + + return (0); +} + +/* + * smb_ctxbuf_len + * + * Return the amount of data stored in the buffer, + * excluding the terminating null character. Similar + * to strlen() + * + * Returns 0 if the ctx is invalid. + */ +int +smb_ctxbuf_len(smb_ctxbuf_t *ctx) +{ + if (ctx == 0 || ctx->basep == 0 || + ctx->curp == 0 || ctx->endp == 0) + return (0); + else + /*LINTED E_PTRDIFF_OVERFLOW*/ + return (ctx->curp - ctx->basep); +} + +/* + * smb_ctxbuf_printf + * + * Move formatted output (based on fmt string) to the buffer + * identified in ctxbuf. Any output characters beyond the buffer + * are discarded and a null character is written at the end of the + * characters actually written. + * + * Returns + * Always return the number of bytes actually written (excluding the + * terminating null). + */ +int +smb_ctxbuf_printf(smb_ctxbuf_t *ctx, const char *fmt, ...) +{ + int n; + va_list args; + + if (ctx == 0 || ctx->basep == 0 || + ctx->curp == 0 || ctx->endp == 0) + return (-1); + + va_start(args, fmt); + /*LINTED E_PTRDIFF_OVERFLOW*/ + n = vsnprintf((char *)ctx->curp, ctx->endp-ctx->curp, fmt, args); + ctx->curp += n; + va_end(args); + + /* + * return the number of bytes moved into the buffer. + */ + return (n); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_domain.c b/usr/src/lib/smbsrv/libsmb/common/smb_domain.c new file mode 100644 index 0000000000..22ea5e61fb --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_domain.c @@ -0,0 +1,394 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file defines the NT domain environment values and the domain + * database interface. The database is a single linked list of + * structures containing domain type, name and SID information. + */ + +#include <strings.h> +#include <unistd.h> +#include <netdb.h> +#include <syslog.h> +#include <synch.h> + +#include <smbsrv/smbinfo.h> +#include <smbsrv/string.h> +#include <smbsrv/ntsid.h> +#include <smbsrv/alloc.h> + +#include <smbsrv/libsmb.h> + + +static void nt_domain_unlist(nt_domain_t *); + +/* + * Valid domain type identifiers as text. This table must be kept + * in step with the nt_domain_type_t enum in ntdomain.h. + */ +static char *nt_domain_type_name[NT_DOMAIN_NUM_TYPES] = { + "null", + "builtin", + "local", + "primary", + "account", + "trusted", + "untrusted" +}; + + +static rwlock_t nt_domain_lock; +static nt_domain_t *nt_domain_list; + +/* + * nt_domain_init + * + * NT domain database one time initialization. This function should + * be called during module installation. + * + * Returns 0 on successful domain initialization. Less than zero otherwise. + */ +int +nt_domain_init(char *resource_domain, uint32_t secmode) +{ + nt_domain_t *domain; + nt_sid_t *sid; + char *sidstr; + char *lsidstr; + char hostname[MAXHOSTNAMELEN]; + + if (rwlock_init(&nt_domain_lock, USYNC_THREAD, NULL)) + return (SMB_DOMAIN_NODOMAIN_SID); + + if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0) { + (void) rwlock_destroy(&nt_domain_lock); + return (SMB_DOMAIN_NOMACHINE_SID); + } + + lsidstr = smb_config_get_localsid(); + + if (lsidstr) { + sid = nt_sid_strtosid(lsidstr); + + if (sid) { + domain = nt_domain_new(NT_DOMAIN_LOCAL, hostname, sid); + (void) nt_domain_add(domain); + free(sid); + } + free(lsidstr); + } else { + (void) rwlock_destroy(&nt_domain_lock); + return (SMB_DOMAIN_NOMACHINE_SID); + } + + if (secmode == SMB_SECMODE_DOMAIN) { + sid = nt_sid_strtosid(NT_BUILTIN_DOMAIN_SIDSTR); + domain = nt_domain_new(NT_DOMAIN_BUILTIN, "BUILTIN", sid); + (void) nt_domain_add(domain); + free(sid); + + smb_config_rdlock(); + sidstr = smb_config_get(SMB_CI_DOMAIN_SID); + if (sidstr) { + sid = nt_sid_strtosid(sidstr); + smb_config_unlock(); + domain = nt_domain_new(NT_DOMAIN_PRIMARY, + resource_domain, sid); + (void) nt_domain_add(domain); + free(sid); + } else { + smb_config_unlock(); + (void) rwlock_destroy(&nt_domain_lock); + return (SMB_DOMAIN_NODOMAIN_SID); + } + + } + return (0); +} + +/* + * nt_domain_new + * + * Allocate and initialize a new domain structure. On success, a pointer to + * the new domain structure is returned. Otherwise a null pointer is returned. + */ +nt_domain_t * +nt_domain_new(nt_domain_type_t type, char *name, nt_sid_t *sid) +{ + nt_domain_t *new_domain; + + if ((name == NULL) || (sid == NULL)) + return (NULL); + + if (type == NT_DOMAIN_NULL || type >= NT_DOMAIN_NUM_TYPES) + return (NULL); + + if ((new_domain = malloc(sizeof (nt_domain_t))) == NULL) + return (NULL); + + bzero(new_domain, sizeof (nt_domain_t)); + new_domain->type = type; + new_domain->name = strdup(name); + new_domain->sid = nt_sid_dup(sid); + + return (new_domain); +} + +/* + * nt_domain_delete + * + * Free the memory used by the specified domain structure. + */ +void +nt_domain_delete(nt_domain_t *domain) +{ + if (domain) { + free(domain->name); + free(domain->sid); + free(domain); + } +} + + +/* + * nt_domain_add + * + * Add a domain structure to the global list. There is no checking + * for duplicates. If it's the primary domain, we save the SID in the + * environment. Returns a pointer to the new domain entry on success. + * Otherwise a null pointer is returned. + */ +nt_domain_t * +nt_domain_add(nt_domain_t *new_domain) +{ + char *sidstr; + + if (new_domain == NULL) + return (NULL); + + (void) rw_wrlock(&nt_domain_lock); + + new_domain->next = nt_domain_list; + nt_domain_list = new_domain; + + if (new_domain->type == NT_DOMAIN_PRIMARY) { + sidstr = nt_sid_format(new_domain->sid); + smb_config_wrlock(); + (void) smb_config_set(SMB_CI_DOMAIN_SID, sidstr); + smb_config_unlock(); + free(sidstr); + } + (void) rw_unlock(&nt_domain_lock); + + return (new_domain); +} + + +/* + * nt_domain_remove + * + * Remove a domain from the global list. The memory + * used by the structure is not freed. + */ +void +nt_domain_remove(nt_domain_t *domain) +{ + (void) rw_wrlock(&nt_domain_lock); + nt_domain_unlist(domain); + (void) rw_unlock(&nt_domain_lock); +} + + +/* + * nt_domain_flush + * + * Flush all domains of the specified type from the list. This is + * useful for things like updating the list of trusted domains. + */ +void +nt_domain_flush(nt_domain_type_t domain_type) +{ + nt_domain_t *domain = nt_domain_list; + + (void) rw_wrlock(&nt_domain_lock); + while (domain) { + if (domain->type == domain_type) { + nt_domain_unlist(domain); + nt_domain_delete(domain); + domain = nt_domain_list; + continue; + } + domain = domain->next; + } + (void) rw_unlock(&nt_domain_lock); +} + +/* + * nt_domain_xlat_type + * + * Translate a domain type into a text string. + */ +char * +nt_domain_xlat_type(nt_domain_type_t domain_type) +{ + if (domain_type < NT_DOMAIN_NUM_TYPES) + return (nt_domain_type_name[domain_type]); + else + return ("unknown"); +} + + +/* + * nt_domain_xlat_type_name + * + * Translate a domain type test string into a domain type. + */ +nt_domain_type_t +nt_domain_xlat_type_name(char *type_name) +{ + int i; + + for (i = 0; i < NT_DOMAIN_NUM_TYPES; ++i) + if (utf8_strcasecmp(nt_domain_type_name[i], type_name) == 0) + return (i); + + return (NT_DOMAIN_NUM_TYPES); +} + + +/* + * nt_domain_lookup_name + * + * Lookup a domain by its domain name. If the domain is in the list, + * a pointer to it is returned. Otherwise a null pointer is returned. + */ +nt_domain_t * +nt_domain_lookup_name(char *domain_name) +{ + nt_domain_t *domain = nt_domain_list; + + (void) rw_rdlock(&nt_domain_lock); + while (domain) { + if (utf8_strcasecmp(domain->name, domain_name) == 0) + break; + + domain = domain->next; + } + (void) rw_unlock(&nt_domain_lock); + + return (domain); +} + + +/* + * nt_domain_lookup_sid + * + * Lookup a domain by its domain SID. If the domain is in the list, + * a pointer to it is returned. Otherwise a null pointer is returned. + */ +nt_domain_t * +nt_domain_lookup_sid(nt_sid_t *domain_sid) +{ + nt_domain_t *domain = nt_domain_list; + + (void) rw_rdlock(&nt_domain_lock); + while (domain) { + if (nt_sid_is_equal(domain->sid, domain_sid)) + break; + + domain = domain->next; + } + (void) rw_unlock(&nt_domain_lock); + + return (domain); +} + + +/* + * nt_domain_lookupbytype + * + * Lookup a domain by its type. The first matching entry in the list + * is returned. Otherwise a null pointer is returned. + */ +nt_domain_t * +nt_domain_lookupbytype(nt_domain_type_t type) +{ + nt_domain_t *domain = nt_domain_list; + + (void) rw_rdlock(&nt_domain_lock); + while (domain) { + if (domain->type == type) + break; + + domain = domain->next; + } + (void) rw_unlock(&nt_domain_lock); + + return (domain); +} + + +/* + * nt_domain_local_sid + * + * Return a pointer to the local domain SID. Each system has a SID that + * represents the local domain, which is named after the local hostname. + * The local domain SID must exist. + */ +nt_sid_t * +nt_domain_local_sid(void) +{ + nt_domain_t *domain = nt_domain_list; + + (void) rw_rdlock(&nt_domain_lock); + while (domain) { + if (domain->type == NT_DOMAIN_LOCAL) + break; + + domain = domain->next; + } + (void) rw_unlock(&nt_domain_lock); + + return (domain->sid); +} + + +static void +nt_domain_unlist(nt_domain_t *domain) +{ + nt_domain_t **ppdomain = &nt_domain_list; + + while (*ppdomain) { + if (*ppdomain == domain) { + *ppdomain = domain->next; + domain->next = NULL; + return; + } + ppdomain = &(*ppdomain)->next; + } +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_door_client.c b/usr/src/lib/smbsrv/libsmb/common/smb_door_client.c new file mode 100644 index 0000000000..7e09b237b3 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_door_client.c @@ -0,0 +1,411 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * User-space door client for SMBd + */ + +#include <fcntl.h> +#include <syslog.h> +#include <door.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <errno.h> + +#include <smbsrv/smbinfo.h> +#include <smbsrv/wintypes.h> +#include <smbsrv/ntstatus.h> +#include <smbsrv/alloc.h> +#include <smbsrv/smb_common_door.h> + +#include <smbsrv/libsmb.h> + +static int smb_door_fildes = -1; + +static char *smbd_desc[] = { + "", + "SmbdJoinDomain", + "SmbdGetParam", + "SmbdSetParam", + "SmbdNetbiosReconfig", + 0 +}; + +/* + * Returns 0 on success. Otherwise, -1. + */ +static int +smbd_door_open(int opcode) +{ + int rc = 0; + + if (smb_door_fildes == -1 && + (smb_door_fildes = open(SMBD_DOOR_NAME, O_RDONLY)) < 0) { + syslog(LOG_ERR, "%s: open %s failed %s", smbd_desc[opcode], + SMBD_DOOR_NAME, strerror(errno)); + rc = -1; + } + + return (rc); +} + +/* + * Return 0 upon success. Otherwise, -1. + */ +static int +smbd_door_check_srv_status(int opcode, smb_dr_ctx_t *dec_ctx) +{ + int status = smb_dr_get_int32(dec_ctx); + int err; + int rc = -1; + + switch (status) { + case SMBD_DOOR_SRV_SUCCESS: + rc = 0; + break; + + case SMBD_DOOR_SRV_ERROR: + err = smb_dr_get_uint32(dec_ctx); + syslog(LOG_ERR, "%s: Encountered door server error %s", + smbd_desc[opcode], strerror(err)); + break; + + default: + syslog(LOG_ERR, "%s: Unknown door server status", + smbd_desc[opcode]); + } + + if (rc != 0) { + if ((err = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + smbd_desc[opcode], strerror(err)); + } + } + + return (rc); +} + +uint32_t +smb_join(smb_joininfo_t *jdi) +{ + door_arg_t arg; + char *buf; + uint32_t used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + uint32_t rc; + int opcode = SMBD_DOOR_JOIN; + + if ((jdi == 0) || (*jdi->domain_name == 0)) { + syslog(LOG_ERR, "%s: invalid parameter(s)", smbd_desc[opcode]); + return (NT_STATUS_INVALID_PARAMETER); + } + + if (smbd_door_open(opcode) == -1) { + syslog(LOG_ERR, "%s: cannot open the door", smbd_desc[opcode]); + return (NT_STATUS_INTERNAL_ERROR); + } + + buf = MEM_MALLOC("smb_door_client", SMBD_DOOR_SIZE); + if (!buf) { + syslog(LOG_ERR, "%s: resource shortage", smbd_desc[opcode]); + return (NT_STATUS_NO_MEMORY); + } + + enc_ctx = smb_dr_encode_start(buf, SMBD_DOOR_SIZE); + if (enc_ctx == 0) { + syslog(LOG_ERR, "%s: encode start failed", smbd_desc[opcode]); + MEM_FREE("smb_door_client", buf); + return (NT_STATUS_INTERNAL_ERROR); + } + + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_uint32(enc_ctx, jdi->mode); + smb_dr_put_string(enc_ctx, jdi->domain_name); + smb_dr_put_string(enc_ctx, jdi->domain_username); + smb_dr_put_string(enc_ctx, jdi->domain_passwd); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + smbd_desc[opcode], strerror(status)); + MEM_FREE("smb_door_client", buf); + return (NT_STATUS_INTERNAL_ERROR); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = SMBD_DOOR_SIZE; + + if (door_call(smb_door_fildes, &arg) < 0) { + syslog(LOG_ERR, "%s: Door call failed %s", smbd_desc[opcode], + strerror(errno)); + MEM_FREE("smb_door_client", buf); + smb_door_fildes = -1; + return (NT_STATUS_INTERNAL_ERROR); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + if (smbd_door_check_srv_status(opcode, dec_ctx) != 0) { + MEM_FREE("smb_door_client", buf); + return (NT_STATUS_INTERNAL_ERROR); + } + + rc = smb_dr_get_uint32(dec_ctx); + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + smbd_desc[opcode], strerror(status)); + MEM_FREE("smb_door_client", buf); + return (NT_STATUS_INTERNAL_ERROR); + } + + MEM_FREE("smb_door_client", buf); + return (rc); +} + +int +smbd_netbios_reconfig() +{ + door_arg_t arg; + char *buf; + uint32_t used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + DWORD rc; + int opcode = SMBD_DOOR_NETBIOS_RECONFIG; + + if (smbd_door_open(opcode) == -1) { + syslog(LOG_ERR, "%s: cannot open the door", smbd_desc[opcode]); + return (1); + } + + buf = MEM_MALLOC("smb_door_client", SMBD_DOOR_SIZE); + if (!buf) { + syslog(LOG_ERR, "%s: resource shortage", smbd_desc[opcode]); + return (1); + } + + enc_ctx = smb_dr_encode_start(buf, SMBD_DOOR_SIZE); + if (enc_ctx == 0) { + syslog(LOG_ERR, "%s: encode start failed", smbd_desc[opcode]); + MEM_FREE("smb_door_client", buf); + return (1); + } + + smb_dr_put_uint32(enc_ctx, opcode); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + smbd_desc[opcode], strerror(status)); + MEM_FREE("smb_door_client", buf); + return (1); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = SMBD_DOOR_SIZE; + + if (door_call(smb_door_fildes, &arg) < 0) { + syslog(LOG_ERR, "%s: Door call failed %s", smbd_desc[opcode], + strerror(errno)); + MEM_FREE("smb_door_client", buf); + smb_door_fildes = -1; + return (1); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + rc = smb_dr_get_uint32(dec_ctx); + + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + smbd_desc[opcode], strerror(status)); + MEM_FREE("smb_door_client", buf); + return (1); + } + MEM_FREE("smb_door_client", buf); + return (rc); +} + +int +smbd_set_param(smb_cfg_id_t id, char *value) +{ + door_arg_t arg; + char *buf; + uint32_t used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + DWORD rc; + int opcode = SMBD_DOOR_PARAM_SET; + + if (smbd_door_open(opcode) == -1) { + syslog(LOG_ERR, "%s: cannot open the door", smbd_desc[opcode]); + return (1); + } + + buf = MEM_MALLOC("smb_door_client", SMBD_DOOR_SIZE); + if (!buf) { + syslog(LOG_ERR, "%s: resource shortage", smbd_desc[opcode]); + return (1); + } + + enc_ctx = smb_dr_encode_start(buf, SMBD_DOOR_SIZE); + if (enc_ctx == 0) { + syslog(LOG_ERR, "%s: encode start failed", smbd_desc[opcode]); + MEM_FREE("smb_door_client", buf); + return (1); + } + + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_uint32(enc_ctx, id); + smb_dr_put_string(enc_ctx, value); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + smbd_desc[opcode], strerror(status)); + MEM_FREE("smb_door_client", buf); + return (1); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = SMBD_DOOR_SIZE; + + if (door_call(smb_door_fildes, &arg) < 0) { + syslog(LOG_ERR, "%s: Door call failed %s", smbd_desc[opcode], + strerror(errno)); + MEM_FREE("smb_door_client", buf); + smb_door_fildes = -1; + return (1); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + rc = smb_dr_get_uint32(dec_ctx); + + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + smbd_desc[opcode], strerror(status)); + MEM_FREE("smb_door_client", buf); + return (1); + } + MEM_FREE("smb_door_client", buf); + return (rc); +} + +int +smbd_get_param(smb_cfg_id_t id, char *value) +{ + door_arg_t arg; + char *buf; + char *tmp = NULL; + uint32_t used; + smb_dr_ctx_t *dec_ctx; + smb_dr_ctx_t *enc_ctx; + int status; + DWORD rc; + int opcode = SMBD_DOOR_PARAM_GET; + + if (smbd_door_open(opcode) == -1) { + syslog(LOG_ERR, "%s: cannot open the door", smbd_desc[opcode]); + return (1); + } + + buf = MEM_MALLOC("smb_door_client", SMBD_DOOR_SIZE); + if (!buf) { + syslog(LOG_ERR, "%s: resource shortage", smbd_desc[opcode]); + return (1); + } + + enc_ctx = smb_dr_encode_start(buf, SMBD_DOOR_SIZE); + if (enc_ctx == 0) { + syslog(LOG_ERR, "%s: encode start failed", smbd_desc[opcode]); + MEM_FREE("smb_door_client", buf); + return (1); + } + + smb_dr_put_uint32(enc_ctx, opcode); + smb_dr_put_uint32(enc_ctx, id); + + if ((status = smb_dr_encode_finish(enc_ctx, &used)) != 0) { + syslog(LOG_ERR, "%s: Encode error %s", + smbd_desc[opcode], strerror(status)); + MEM_FREE("smb_door_client", buf); + return (1); + } + + arg.data_ptr = buf; + arg.data_size = used; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = buf; + arg.rsize = SMBD_DOOR_SIZE; + + if (door_call(smb_door_fildes, &arg) < 0) { + syslog(LOG_ERR, "%s: Door call failed %s", smbd_desc[opcode], + strerror(errno)); + MEM_FREE("smb_door_client", buf); + smb_door_fildes = -1; + return (1); + } + + dec_ctx = smb_dr_decode_start(arg.data_ptr, arg.data_size); + rc = smb_dr_get_uint32(dec_ctx); + tmp = smb_dr_get_string(dec_ctx); + (void) strcpy(value, tmp); + + if ((status = smb_dr_decode_finish(dec_ctx)) != 0) { + syslog(LOG_ERR, "%s: Decode error %s", + smbd_desc[opcode], strerror(status)); + MEM_FREE("smb_door_client", buf); + return (1); + } + MEM_FREE("smb_door_client", buf); + return (rc); +} + +int +smbd_get_security_mode(int *mode) +{ + char buf[64]; + int rc; + + buf[0] = '\0'; + rc = smbd_get_param(SMB_CI_SECURITY, buf); + *mode = smb_config_secmode_fromstr(buf); + return (rc); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_door_encdec.c b/usr/src/lib/smbsrv/libsmb/common/smb_door_encdec.c new file mode 100644 index 0000000000..3734f6e49c --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_door_encdec.c @@ -0,0 +1,313 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdlib.h> +#include <strings.h> +#include <rpc/xdr.h> +#include <errno.h> +#include <smbsrv/libsmb.h> +#include <smbsrv/smb_xdr.h> +#include <smbsrv/smb_common_door.h> +#include <smbsrv/smb_door_svc.h> + +/* + * smb_dr_decode_common + * + * This function can be used to decode both door request and result buffer. + * pre-condition: data is non-null pointer, and is bzero'd. + */ +int +smb_dr_decode_common(char *buf, size_t len, xdrproc_t proc, void *data) +{ + XDR xdrs; + int rc = 0; + + if (!data) { + syslog(LOG_ERR, "smb_dr_decode_common: invalid param"); + return (-1); + } + + xdrmem_create(&xdrs, buf, len, XDR_DECODE); + if (!proc(&xdrs, data)) { + rc = -1; + } + xdr_destroy(&xdrs); + return (rc); +} + +/* + * smb_dr_encode_common + * + * This function can be used to encode both request and result door buffer. + * The 'opcode' paramater is set to the 'opcode' of the operation to be invoked + * on the server, by the client. The server sets the same 'opcode' paramater + * to indicate the 'status' of the door call. + * + * This function will first encode integer value 'opcode' (opcode/status), + * followed by the data (which will be encoded via the specified XDR routine). + * + * Returns encoded buffer upon success. Otherwise, returns NULL. + */ +char * +smb_dr_encode_common(uint_t opcode, void *data, xdrproc_t proc, size_t *len) +{ + XDR xdrs; + char *buf; + + if (proc && !data) { + syslog(LOG_ERR, "smb_dr_encode_common: invalid param"); + *len = 0; + return (NULL); + } + + *len = xdr_sizeof(xdr_uint32_t, &opcode); + if (proc) + *len += xdr_sizeof(proc, data); + buf = (char *)malloc(*len); + if (!buf) { + syslog(LOG_ERR, "smb_dr_encode_common: resource shortage"); + *len = 0; + return (NULL); + } + xdrmem_create(&xdrs, buf, *len, XDR_ENCODE); + if (!xdr_uint32_t(&xdrs, &opcode)) { + syslog(LOG_DEBUG, "smb_dr_encode_common: encode error 1"); + free(buf); + *len = 0; + xdr_destroy(&xdrs); + return (NULL); + } + + if (proc && !proc(&xdrs, data)) { + syslog(LOG_DEBUG, "smb_dr_encode_common: encode error 2"); + free(buf); + buf = NULL; + *len = 0; + } + + xdr_destroy(&xdrs); + return (buf); +} + +/* + * Get the opcode of the door argument buffer. + */ +int +smb_dr_get_opcode(char *argp, size_t arg_size) +{ + int opcode; + + if (smb_dr_decode_common(argp, arg_size, xdr_uint32_t, &opcode) != 0) + opcode = -1; + return (opcode); +} + +/* + * Set the opcode of the door argument buffer. + */ +char * +smb_dr_set_opcode(uint32_t opcode, size_t *len) +{ + char *buf; + + buf = smb_dr_encode_common(opcode, NULL, NULL, len); + return (buf); +} + +/* + * Get the status of the door result buffer. + */ +int +smb_dr_get_res_stat(char *rbufp, size_t rbuf_size) +{ + int stat; + if (smb_dr_decode_common(rbufp, rbuf_size, xdr_uint32_t, &stat) != 0) + stat = -1; + return (stat); +} + +/* + * Set the status of the door result buffer. + */ +char * +smb_dr_set_res_stat(uint32_t stat, size_t *len) +{ + char *buf; + + buf = smb_dr_encode_common(stat, NULL, NULL, len); + return (buf); +} + +char * +smb_dr_encode_res_token(smb_token_t *token, size_t *len) +{ + smb_dr_bytes_t res; + char *buf = NULL; + + res.bytes_val = smb_token_mkselfrel(token, &res.bytes_len); + if (!res.bytes_val) { + syslog(LOG_ERR, "smb_dr_encode_res_token: mkselfrel error"); + *len = 0; + return (NULL); + } + + if ((buf = smb_dr_encode_common(SMB_DR_OP_SUCCESS, &res, + xdr_smb_dr_bytes_t, len)) == NULL) { + syslog(LOG_ERR, "smb_dr_encode_res_token: failed"); + *len = 0; + free(res.bytes_val); + return (NULL); + + } + free(res.bytes_val); + return (buf); +} + +char * +smb_dr_encode_kshare(smb_dr_kshare_t *kshare, size_t *buflen) +{ + smb_dr_bytes_t res; + char *buf = NULL; + + res.bytes_val = smb_kshare_mkselfrel(kshare, &res.bytes_len); + + free(kshare->k_path); + free(kshare->k_sharename); + + if (!res.bytes_val) + return (NULL); + + buf = smb_dr_encode_common(SMB_KDR_SHARE, &res, xdr_smb_dr_bytes_t, + buflen); + + free(res.bytes_val); + + return (buf); +} + +/* + * smb_kshare_mkselfrel + * + * encode: structure -> flat buffer (buffer size) + * Pre-condition: kshare is non-null. + */ + +uint8_t * +smb_kshare_mkselfrel(smb_dr_kshare_t *kshare, uint32_t *len) +{ + uint8_t *buf; + XDR xdrs; + + if (!kshare) + return (NULL); + + *len = xdr_sizeof(xdr_smb_dr_kshare_t, kshare); + buf = (uint8_t *)malloc(*len); + if (!buf) + return (NULL); + + xdrmem_create(&xdrs, (const caddr_t)buf, *len, XDR_ENCODE); + + if (!xdr_smb_dr_kshare_t(&xdrs, kshare)) { + *len = 0; + free(buf); + buf = NULL; + } + + xdr_destroy(&xdrs); + return (buf); +} + +char * +smb_dr_encode_string(uint32_t opcode, char *str, size_t *len) +{ + char *buf; + smb_dr_string_t res; + + res.buf = str; + + if ((buf = smb_dr_encode_common(opcode, &res, + xdr_smb_dr_string_t, len)) == NULL) + syslog(LOG_ERR, "smb_dr_encode_string: failed"); + return (buf); +} + +char * +smb_dr_decode_string(char *buf, size_t len) +{ + smb_dr_string_t res; + char *str = NULL; + + bzero(&res, sizeof (smb_dr_string_t)); + if (smb_dr_decode_common(buf, len, xdr_smb_dr_string_t, + &res) == 0) { + str = res.buf; + } else { + syslog(LOG_ERR, "smb_dr_decode_string: failed"); + } + return (str); +} + +netr_client_t * +smb_dr_decode_arg_get_token(char *buf, size_t len) +{ + smb_dr_bytes_t arg; + netr_client_t *clnt_info; + + bzero(&arg, sizeof (smb_dr_bytes_t)); + if (smb_dr_decode_common(buf, len, xdr_smb_dr_bytes_t, &arg) + != 0) { + syslog(LOG_ERR, "smb_dr_decode_arg_get_token: failed"); + xdr_free(xdr_smb_dr_bytes_t, (char *)&arg); + return (NULL); + } + clnt_info = netr_client_mkabsolute(arg.bytes_val, + arg.bytes_len); + xdr_free(xdr_smb_dr_bytes_t, (char *)&arg); + return (clnt_info); +} + +void +smb_dr_ulist_free(smb_dr_ulist_t *ulist) +{ + int i; + smb_dr_user_ctx_t *uinfo; + + if (!ulist) + return; + + for (i = 0; i < ulist->dul_cnt; i++) { + uinfo = &ulist->dul_users[i]; + + if (!uinfo) + continue; + + xdr_free(xdr_smb_dr_ulist_t, (char *)ulist); + } + + free(ulist); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_doorclnt.c b/usr/src/lib/smbsrv/libsmb/common/smb_doorclnt.c new file mode 100644 index 0000000000..36373f10f0 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_doorclnt.c @@ -0,0 +1,148 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * User-space door client routines for both SMB daemon and CLIs. + */ + +#include <fcntl.h> +#include <syslog.h> +#include <door.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/mman.h> +#include <smbsrv/libsmb.h> +#include <smbsrv/wintypes.h> +#include <smbsrv/smb_door_svc.h> +#include <smbsrv/smb_common_door.h> + +/* + * Returns 0 on success. Otherwise, -1. + */ +int +smb_dr_clnt_open(int *fd, char *path, char *op_desc) +{ + int rc = 0; + + if (!op_desc) + op_desc = "unknown operation"; + + if (!path || !fd) + return (-1); + + if ((*fd = open(path, O_RDONLY)) < 0) { + syslog(LOG_ERR, "%s: open %s failed %s", op_desc, + path, strerror(errno)); + rc = -1; + } + + return (rc); +} + +/* + * smb_dr_clnt_call + * + * This function will make a door call to the server function + * associated with the door descriptor fd. The specified door + * request buffer (i.e. argp) will be passed as the argument to the + * door_call(). Upon success, the result buffer is returned. Otherwise, + * NULL pointer is returned. The size of the result buffer is returned + * via rbufsize. + */ +char * +smb_dr_clnt_call(int fd, char *argp, size_t arg_size, size_t *rbufsize, + char *op_desc) +{ + door_arg_t arg; + + if (!argp) { + syslog(LOG_ERR, "smb_dr_clnt_call: invalid parameter"); + return (NULL); + } + + arg.data_ptr = argp; + arg.data_size = arg_size; + arg.desc_ptr = NULL; + arg.desc_num = 0; + arg.rbuf = argp; + arg.rsize = arg_size; + + if (!op_desc) + op_desc = "unknown operation"; + + if (door_call(fd, &arg) < 0) { + syslog(LOG_ERR, "%s: Door call failed %s", op_desc, + strerror(errno)); + free(argp); + argp = NULL; + return (NULL); + } + + if (smb_dr_get_res_stat(arg.data_ptr, arg.rsize) + != SMB_DR_OP_SUCCESS) { + smb_dr_clnt_free(argp, arg_size, arg.rbuf, arg.rsize); + *rbufsize = 0; + return (NULL); + } + *rbufsize = arg.rsize; + return (arg.data_ptr); +} + +/* + * smb_dr_clnt_free + * + * This function should be invoked to free both the argument/result door buffer + * regardless of the status of the door call. + * + * The doorfs allocates a new buffer if the result buffer passed by the client + * is too small. This function will munmap if that happens. + */ +/*ARGSUSED*/ +void +smb_dr_clnt_free(char *argp, size_t arg_size, char *rbufp, size_t rbuf_size) +{ + if (argp) { + if (argp == rbufp) { + free(argp); + argp = NULL; + } else if (rbufp) { + free(argp); + argp = NULL; + if (munmap(rbufp, rbuf_size) != 0) { + syslog(LOG_ERR, "munmap failed"); + } + } + } else { + if (rbufp) { + if (munmap(rbufp, rbuf_size) != 0) { + syslog(LOG_ERR, "munmap failed"); + } + } + } +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_downcalls.c b/usr/src/lib/smbsrv/libsmb/common/smb_downcalls.c new file mode 100644 index 0000000000..2f76b4e875 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_downcalls.c @@ -0,0 +1,195 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Down calls to SMB Kmod for obtaining various kernel door services. + */ + +#include <syslog.h> +#include <strings.h> +#include <stdlib.h> +#include <string.h> +#include <rpc/xdr.h> +#include <smbsrv/smb_door_svc.h> +#include <smbsrv/smb_common_door.h> +#include <smbsrv/libsmb.h> + +/* indexed via opcode (smb_kdr_opcode_t) */ +char *smb_dwncall_info[] = { + "SmbDwncallNumUser", + "SmbDwncallUserList", + "SmbDwncallShare", + 0 +}; + +static smb_dwncall_get_desc_t get_dwncall_desc; + +int +smb_dwncall_install_callback(smb_dwncall_get_desc_t get_desc_cb) +{ + if (!get_desc_cb) + return (-1); + + get_dwncall_desc = get_desc_cb; + return (0); +} + +int +smb_dwncall_init_fd(uint_t opcode) +{ + int fd; + + if (!get_dwncall_desc) { + syslog(LOG_DEBUG, "%s: failed (unable to get fd)", + smb_dwncall_info[opcode]); + return (-1); + } + + if ((fd = get_dwncall_desc()) == -1) { + syslog(LOG_ERR, "%s: failed (invalid fd)", + smb_dwncall_info[opcode]); + return (-1); + } + + return (fd); +} + +uint64_t +smb_dwncall_user_num() +{ + char *buf, *rbufp; + size_t buflen, rbufsize; + int64_t num; + uint_t opcode = SMB_KDR_USER_NUM; + int fd; + + if ((fd = smb_dwncall_init_fd(opcode)) < 0) + return (0); + + buf = smb_dr_set_opcode(opcode, &buflen); + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smb_dwncall_info[opcode]); + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_uint32_t, &num) != 0) { + num = -1; + } + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + return (num); +} + +/* + * smb_dwncall_get_users + * + * The calling function must free the output parameter 'users'. + */ +int +smb_dwncall_get_users(int offset, smb_dr_ulist_t *users) +{ + char *buf = NULL, *rbufp; + size_t buflen, rbufsize; + uint_t opcode = SMB_KDR_USER_LIST; + int fd, rc = -1; + + bzero(users, sizeof (smb_dr_ulist_t)); + if ((fd = smb_dwncall_init_fd(opcode)) < 0) + return (-1); + + buf = smb_dr_encode_common(opcode, &offset, xdr_uint32_t, &buflen); + if (!buf) { + syslog(LOG_ERR, "smb_dwncall_get_users: encode error"); + return (-1); + } + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smb_dwncall_info[opcode]); + + + if (rbufp) { + rc = smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_smb_dr_ulist_t, users); + if (rc) + syslog(LOG_ERR, "smb_dwncall_get_users: decode error"); + else + rc = users->dul_cnt; + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + return (rc); +} + +/* + * smb_dwncall_share() + * + * This is a downcall to the kernel that is executed + * upon share enable and disable. + */ + +int +smb_dwncall_share(int op, char *path, char *sharename) +{ + char *buf = NULL, *rbufp; + size_t buflen, rbufsize; + int32_t opcode = SMB_KDR_SHARE; + smb_dr_kshare_t kshare; + int fd, rc = -1; + + if ((op != LMSHR_ADD) && + (op != LMSHR_DELETE)) + return (-1); + + if ((fd = smb_dwncall_init_fd(opcode)) < 0) { + syslog(LOG_ERR, "smb_dwncall_share: init error"); + return (-1); + } + + kshare.k_op = op; + kshare.k_path = strdup(path); + kshare.k_sharename = strdup(sharename); + + buf = smb_dr_encode_kshare(&kshare, &buflen); + + if (!buf) { + syslog(LOG_ERR, "smb_dwncall_share: encode error"); + return (-1); + } + + rbufp = smb_dr_clnt_call(fd, buf, buflen, &rbufsize, + smb_dwncall_info[opcode]); + + if (rbufp) { + if (smb_dr_decode_common(rbufp + SMB_DR_DATA_OFFSET, + rbufsize - SMB_DR_DATA_OFFSET, xdr_int32_t, &rc) != 0) { + rc = -1; + } + } + + smb_dr_clnt_free(buf, buflen, rbufp, rbufsize); + return (rc); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_group_door_encdec.c b/usr/src/lib/smbsrv/libsmb/common/smb_group_door_encdec.c new file mode 100644 index 0000000000..0b29764926 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_group_door_encdec.c @@ -0,0 +1,337 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <strings.h> +#include <smbsrv/libsmb.h> +#include <smbsrv/smb_xdr.h> +#include <smbsrv/smb_door_svc.h> + +/* + * smb_grplist_mkselfrel + * + * encode: structure -> flat buffer (buffer size) + * Pre-condition: obj is non-null. + */ +static uint8_t * +smb_grplist_mkselfrel(ntgrp_list_t *obj, uint32_t *len) +{ + uint8_t *buf; + XDR xdrs; + + if (!obj) { + syslog(LOG_ERR, "smb_grplist_mkselfrel: invalid parameter"); + return (NULL); + } + *len = xdr_sizeof(xdr_ntgrp_list_t, obj); + buf = (uint8_t *)malloc(*len); + + xdrmem_create(&xdrs, (const caddr_t)buf, *len, XDR_ENCODE); + + if (!xdr_ntgrp_list_t(&xdrs, obj)) { + syslog(LOG_ERR, "smb_grplist_mkselfrel: XDR encode error"); + free(buf); + *len = 0; + buf = NULL; + } + + xdr_destroy(&xdrs); + return (buf); +} + +/* + * smb_grplist_mkabsolute + * + * decode: flat buffer -> structure + */ +static ntgrp_list_t * +smb_grplist_mkabsolute(uint8_t *buf, uint32_t len) +{ + ntgrp_list_t *obj; + XDR xdrs; + + xdrmem_create(&xdrs, (const caddr_t)buf, len, XDR_DECODE); + + if ((obj = (ntgrp_list_t *) + malloc(sizeof (ntgrp_list_t))) == 0) { + syslog(LOG_ERR, "smb_grplist_mkabsolute: resource shortage"); + xdr_destroy(&xdrs); + return (NULL); + } + bzero(obj, sizeof (ntgrp_list_t)); + if (!xdr_ntgrp_list_t(&xdrs, obj)) { + syslog(LOG_ERR, "smb_grplist_mkabsolute: XDR decode error"); + smb_group_free_list(obj, 1); + obj = NULL; + } + + xdr_destroy(&xdrs); + return (obj); +} + +/* + * smb_grpmemberlist_mkselfrel + * + * encode: structure -> flat buffer (buffer size) + * Pre-condition: obj is non-null. + */ +static uint8_t * +smb_grpmemberlist_mkselfrel(ntgrp_member_list_t *obj, uint32_t *len) +{ + uint8_t *buf; + XDR xdrs; + + if (!obj) { + syslog(LOG_ERR, + "smb_grpmemberlist_mkselfrel: invalid parameter"); + return (NULL); + } + *len = xdr_sizeof(xdr_ntgrp_member_list_t, obj); + buf = (uint8_t *)malloc(*len); + xdrmem_create(&xdrs, (const caddr_t)buf, *len, XDR_ENCODE); + if (!xdr_ntgrp_member_list_t(&xdrs, obj)) { + syslog(LOG_ERR, + "smb_grpmemberlist_mkselfrel: XDR encode error"); + free(buf); + *len = 0; + buf = NULL; + } + + xdr_destroy(&xdrs); + return (buf); +} + +/* + * ntgrp_list_mkabsolute + * + * decode: flat buffer -> structure + */ +static ntgrp_member_list_t * +smb_grpmemberlist_mkabsolute(uint8_t *buf, uint32_t len) +{ + ntgrp_member_list_t *obj = NULL; + XDR xdrs; + + xdrmem_create(&xdrs, (const caddr_t)buf, len, XDR_DECODE); + + if ((obj = (ntgrp_member_list_t *) + malloc(sizeof (ntgrp_member_list_t))) == 0) { + xdr_destroy(&xdrs); + syslog(LOG_ERR, + "smb_grpmemberlist_mkabsolute: resource shortage"); + return (NULL); + } + bzero(obj, sizeof (ntgrp_member_list_t)); + bzero(obj->members, SMB_GROUP_PER_LIST * sizeof (members_list)); + if (!xdr_ntgrp_member_list_t(&xdrs, obj)) { + syslog(LOG_ERR, + "smb_grpmemberlist_mkabsolute: XDR decode error"); + smb_group_free_memberlist(obj, 1); + obj = NULL; + } + + xdr_destroy(&xdrs); + return (obj); +} + +/* + * smb_privlist_mkselfrel + * + * encode: structure -> flat buffer (buffer size) + * Pre-condition: obj is non-null. + */ +static uint8_t * +smb_grpprivlist_mkselfrel(ntpriv_list_t *obj, uint32_t *len) +{ + uint8_t *buf; + XDR xdrs; + + if (!obj) { + syslog(LOG_ERR, + "smb_grpprivlist_mkselfrel: invalid parameter"); + return (NULL); + } + *len = xdr_sizeof(xdr_ntpriv_list_t, obj); + buf = (uint8_t *)malloc(*len); + if (!buf) { + syslog(LOG_ERR, + "smb_grpprivlist_mkselfrel: resource shortage"); + return (NULL); + } + xdrmem_create(&xdrs, (const caddr_t)buf, *len, XDR_ENCODE); + if (!xdr_ntpriv_list_t(&xdrs, obj)) { + syslog(LOG_ERR, + "smb_grpprivlist_mkselfrel: XDR encode error"); + *len = 0; + free(buf); + buf = NULL; + } + xdr_destroy(&xdrs); + return (buf); +} + +/* + * smb_privlist_mkabsolute + * + * decode: flat buffer -> structure + */ +static ntpriv_list_t * +smb_grpprivlist_mkabsolute(uint8_t *buf, uint32_t len) +{ + ntpriv_list_t *obj = NULL; + XDR xdrs; + uint32_t status; + int length = 0, num_privs = 0; + + xdrmem_create(&xdrs, (const caddr_t)buf, len, XDR_DECODE); + status = smb_group_priv_num(&num_privs); + if (status != 0) { + syslog(LOG_ERR, + "smb_grpprivlist_mkabsolute: Cannot get privlist."); + xdr_destroy(&xdrs); + return (NULL); + } + + if (num_privs > 0) { + length = sizeof (int) + (num_privs * sizeof (privs_t)); + if ((obj = (ntpriv_list_t *)malloc(length)) == 0) { + syslog(LOG_ERR, + "smb_grpprivlist_mkabsolute: resource shortage"); + xdr_destroy(&xdrs); + return (NULL); + } + } + bzero(obj, sizeof (ntpriv_list_t)); + bzero(obj->privs, num_privs * sizeof (privs_t)); + if (!xdr_ntpriv_list_t(&xdrs, obj)) { + syslog(LOG_ERR, "smb_grpprivlist_mkabsolute: XDR decode error"); + smb_group_free_privlist(obj, 1); + obj = NULL; + } + xdr_destroy(&xdrs); + return (obj); +} + +char * +smb_dr_encode_grp_list(uint32_t opcode, ntgrp_list_t *list, + size_t *len) +{ + char *buf; + smb_dr_bytes_t arg; + + arg.bytes_val = smb_grplist_mkselfrel(list, &arg.bytes_len); + + buf = smb_dr_encode_common(opcode, &arg, xdr_smb_dr_bytes_t, len); + free(arg.bytes_val); + return (buf); +} + +ntgrp_list_t * +smb_dr_decode_grp_list(char *buf, size_t len) +{ + smb_dr_bytes_t arg; + ntgrp_list_t *list; + + bzero(&arg, sizeof (smb_dr_bytes_t)); + if (smb_dr_decode_common(buf, len, xdr_smb_dr_bytes_t, &arg) + != 0) { + syslog(LOG_ERR, "smb_dr_decode_grplist: XDR decode error"); + xdr_free(xdr_smb_dr_bytes_t, (char *)&arg); + return (NULL); + } + list = smb_grplist_mkabsolute(arg.bytes_val, arg.bytes_len); + xdr_free(xdr_smb_dr_bytes_t, (char *)&arg); + return (list); +} + +char * +smb_dr_encode_grp_memberlist(uint32_t opcode, + ntgrp_member_list_t *list, size_t *len) +{ + char *buf; + smb_dr_bytes_t arg; + + arg.bytes_val = smb_grpmemberlist_mkselfrel(list, &arg.bytes_len); + + buf = smb_dr_encode_common(opcode, &arg, xdr_smb_dr_bytes_t, len); + free(arg.bytes_val); + return (buf); +} + +ntgrp_member_list_t * +smb_dr_decode_grp_memberlist(char *buf, size_t len) +{ + smb_dr_bytes_t arg; + ntgrp_member_list_t *list; + + bzero(&arg, sizeof (smb_dr_bytes_t)); + if (smb_dr_decode_common(buf, len, xdr_smb_dr_bytes_t, &arg) + != 0) { + syslog(LOG_ERR, + "smb_dr_decode_grpmemberlist: XDR decode error"); + xdr_free(xdr_smb_dr_bytes_t, (char *)&arg); + return (NULL); + } + list = smb_grpmemberlist_mkabsolute(arg.bytes_val, arg.bytes_len); + xdr_free(xdr_smb_dr_bytes_t, (char *)&arg); + return (list); +} + +char * +smb_dr_encode_grp_privlist(uint32_t opcode, + ntpriv_list_t *list, size_t *len) +{ + char *buf; + smb_dr_bytes_t arg; + + arg.bytes_val = smb_grpprivlist_mkselfrel(list, &arg.bytes_len); + + buf = smb_dr_encode_common(opcode, &arg, xdr_smb_dr_bytes_t, len); + free(arg.bytes_val); + return (buf); +} + +ntpriv_list_t * +smb_dr_decode_grp_privlist(char *buf, size_t len) +{ + smb_dr_bytes_t arg; + ntpriv_list_t *list; + + bzero(&arg, sizeof (smb_dr_bytes_t)); + if (smb_dr_decode_common(buf, len, xdr_smb_dr_bytes_t, &arg) + != 0) { + syslog(LOG_ERR, "smb_dr_decode_grp_privlist: XDR decode error"); + xdr_free(xdr_smb_dr_bytes_t, (char *)&arg); + return (NULL); + } + list = smb_grpprivlist_mkabsolute(arg.bytes_val, arg.bytes_len); + xdr_free(xdr_smb_dr_bytes_t, (char *)&arg); + return (list); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_group_xdr.c b/usr/src/lib/smbsrv/libsmb/common/smb_group_xdr.c new file mode 100644 index 0000000000..723a6471f9 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_group_xdr.c @@ -0,0 +1,158 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include <rpc/rpc.h> +#include <smbsrv/libsmb.h> + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file was originally generated using rpcgen. + */ + +bool_t +xdr_ntgrp_dr_arg_t(xdrs, objp) + XDR *xdrs; + ntgrp_dr_arg_t *objp; +{ + if (!xdr_string(xdrs, &objp->gname, ~0)) + return (FALSE); + if (!xdr_string(xdrs, &objp->desc, ~0)) + return (FALSE); + if (!xdr_string(xdrs, &objp->member, ~0)) + return (FALSE); + if (!xdr_string(xdrs, &objp->newgname, ~0)) + return (FALSE); + if (!xdr_uint32_t(xdrs, &objp->privid)) + return (FALSE); + if (!xdr_uint32_t(xdrs, &objp->priv_attr)) + return (FALSE); + if (!xdr_int(xdrs, &objp->offset)) + return (FALSE); + if (!xdr_string(xdrs, &objp->scope, ~0)) + return (FALSE); + if (!xdr_int(xdrs, &objp->type)) + return (FALSE); + if (!xdr_int(xdrs, &objp->count)) + return (FALSE); + if (!xdr_uint32_t(xdrs, &objp->ntstatus)) + return (FALSE); + return (TRUE); +} + +bool_t +xdr_ntgrp_t(xdrs, objp) + XDR *xdrs; + ntgrp_t *objp; +{ + if (!xdr_uint32_t(xdrs, &objp->rid)) + return (FALSE); + if (!xdr_string(xdrs, &objp->name, ~0)) + return (FALSE); + if (!xdr_string(xdrs, &objp->desc, ~0)) + return (FALSE); + if (!xdr_string(xdrs, &objp->type, ~0)) + return (FALSE); + if (!xdr_string(xdrs, &objp->sid, ~0)) + return (FALSE); + if (!xdr_uint32_t(xdrs, &objp->attr)) + return (FALSE); + return (TRUE); +} + +bool_t +xdr_ntgrp_list_t(xdrs, objp) + XDR *xdrs; + ntgrp_list_t *objp; +{ + if (!xdr_int(xdrs, &objp->cnt)) + return (FALSE); + if (!xdr_vector(xdrs, (char *)objp->groups, SMB_GROUP_PER_LIST, + sizeof (ntgrp_t), (xdrproc_t)xdr_ntgrp_t)) + return (FALSE); + return (TRUE); +} + +bool_t +xdr_members_list(xdrs, objp) + XDR *xdrs; + members_list *objp; +{ + if (!xdr_string(xdrs, objp, ~0)) + return (FALSE); + return (TRUE); +} + +bool_t +xdr_ntgrp_member_list_t(xdrs, objp) + XDR *xdrs; + ntgrp_member_list_t *objp; +{ + if (!xdr_uint32_t(xdrs, &objp->rid)) + return (FALSE); + if (!xdr_int(xdrs, &objp->cnt)) + return (FALSE); + if (!xdr_vector(xdrs, (char *)objp->members, SMB_GROUP_PER_LIST, + sizeof (members_list), (xdrproc_t)xdr_members_list)) + return (FALSE); + return (TRUE); +} + +bool_t +xdr_ntpriv_t(xdrs, objp) + XDR *xdrs; + ntpriv_t *objp; +{ + if (!xdr_uint32_t(xdrs, &objp->id)) + return (FALSE); + if (!xdr_string(xdrs, &objp->name, ~0)) + return (FALSE); + return (TRUE); +} + +bool_t +xdr_privs_t(xdrs, objp) + XDR *xdrs; + privs_t *objp; +{ + if (!xdr_pointer(xdrs, (char **)objp, sizeof (ntpriv_t), + (xdrproc_t)xdr_ntpriv_t)) + return (FALSE); + return (TRUE); +} + +bool_t +xdr_ntpriv_list_t(xdrs, objp) + XDR *xdrs; + ntpriv_list_t *objp; +{ + if (!xdr_int(xdrs, &objp->cnt)) + return (FALSE); + if (!xdr_vector(xdrs, (char *)objp->privs, objp->cnt, + sizeof (privs_t), (xdrproc_t)xdr_privs_t)) + return (FALSE); + return (TRUE); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_ht.c b/usr/src/lib/smbsrv/libsmb/common/smb_ht.c new file mode 100644 index 0000000000..d167931539 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_ht.c @@ -0,0 +1,639 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Generic hash table library. The hash table is an array of pointers + * to items. Hash collisions are handled using linked lists from the + * table entries. A handle is associated with each table, which is used + * to maintain the hash table. + * + * +------+ +-------+ +----+ +----+ + * |handle|---> |index 0|--->|item|--->|item|---> + * | ... | +-------+ +----+ +----+ + * | ... | |index 1|---> + * +------+ +-------+ +----+ +----+ +----+ + * |index 2|--->|item|--->|item|--->|item|---> + * +-------+ +----+ +----+ +----+ + * | ... |---> + * +-------+ + * | ... |---> + * +-------+ + * |index n|---> + * +-------+ + * + */ + +#include <stdlib.h> +#include <strings.h> +#include <smbsrv/hash_table.h> + +static size_t ht_default_hash(HT_HANDLE *handle, const char *key); + +/* + * ht_is_power2 + * + * Inline function to determine if a value is a power of two. This + * function is used by the library to validate the table size when + * a new table is created. + * + * Returns 1 if value given is power of two, otherwise returns 0. + */ +static size_t +ht_is_power2(size_t value) +{ + return (((value & (value - 1)) == 0)? 1 : 0); +} + + +/* + * ht_create_table + * + * Create a hash table. The table size must be a positive integer and + * must be a power of two. The key size must be a positive integer. + * For null terminated keys, the key size does not need to include the + * null terminating character. The type of key is indicated by the + * flags (see hash_table.h). + * + * The handle and the table are are malloc'd using a single call, to + * avoid two allocations. The table is located immediately after the + * handle. + * + * On success a pointer to an opaque handle is returned. Otherwise a + * null pointer is returned. + */ +HT_HANDLE * +ht_create_table(size_t table_size, size_t key_size, size_t flags) +{ + HT_HANDLE *ht; + size_t msize; + size_t i; + + if ((table_size == 0) || (key_size == 0)) + return (NULL); + + if (ht_is_power2(table_size) == 0) + return (NULL); + + msize = sizeof (HT_HANDLE) + (sizeof (HT_TABLE_ENTRY) * table_size); + + if ((ht = (HT_HANDLE *)malloc(msize)) == 0) + return (NULL); + + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + ht->ht_table = (HT_TABLE_ENTRY *)((char *)ht + sizeof (HT_HANDLE)); + ht->ht_table_size = table_size; + ht->ht_table_mask = table_size - 1; + ht->ht_key_size = key_size; + ht->ht_total_items = 0; + ht->ht_flags = flags; + ht->ht_hash = ht_default_hash; + ht->ht_callback = 0; + ht->ht_sequence = random(); + ht->ht_cmp = ((flags & HTHF_FIXED_KEY) == 0) + ? (HT_CMP)strncmp : (HT_CMP)memcmp; + + for (i = 0; i < table_size; i++) + bzero(&ht->ht_table[i], sizeof (HT_TABLE_ENTRY)); + + return (ht); +} + + +/* + * ht_destroy_table + * + * Destroy a hash table. All entries in the table are removed, which + * may invoke the callback if it's installed, and the memory is freed. + */ +void +ht_destroy_table(HT_HANDLE *handle) +{ + HT_ITEM *item; + HT_ITERATOR iterator; + + if (handle == 0) + return; + + /* To remove marked entries */ + (void) ht_clean_table(handle); + while ((item = ht_findfirst(handle, &iterator)) != 0) + (void) ht_remove_item(handle, item->hi_key); + + free(handle); +} + + +/* + * ht_get_total_items + * + * Return the total number of items in the table. Returns -1 if the + * handle is invalid. + */ +size_t +ht_get_total_items(HT_HANDLE *handle) +{ + if (handle == 0) + return ((size_t)-1); + + return (handle->ht_total_items); +} + + +/* + * ht_default_hash + * + * Default hash function to compute the table index (hash value) based + * on the specified key. This will identify the location for the + * corresponding item in the hash table. The handle and key pointers + * should be validated before this function is called. + * + * Returns the table index location for the item. + */ +static size_t +ht_default_hash(HT_HANDLE *handle, const char *key) +{ + unsigned int hash_ndx = 0; + size_t rval; + + if ((handle->ht_flags & HTHF_FIXED_KEY) == 0) { + while (*key) { + hash_ndx += *key; + ++key; + } + } else { + int key_len = handle->ht_key_size; + + while (key_len--) { + hash_ndx += *key; + ++key; + } + } + + rval = (hash_ndx * HASH_MESH_VALUE) & handle->ht_table_mask; + return (rval); +} + + +/* + * ht_set_cmpfn + * + * Replace the current compare function. As the this is function + * for comparing items' key, it should not be called while there are + * items in the table. + */ +void +ht_set_cmpfn(HT_HANDLE *handle, HT_CMP cmpfn) +{ + if (handle) + handle->ht_cmp = cmpfn; +} + +/* + * ht_add_item + * + * Adds an item to a hash table. The hash table is identified by the + * handle and the key is used to generate a hashed index. The data + * item can be null; it is never dereferenced. We don't check for + * duplicates. If duplicate keys are added to the table, the last + * item added will be to the front of the duplicate list. + * + * The table sequence number may be modified here. + * + * If the item is successfully inserted, a pointer to the item object + * is returned. Otherwise a null pointer is returned. + */ +HT_ITEM * +ht_add_item(HT_HANDLE *handle, const char *key, const void *data) +{ + size_t h_index, key_len; + size_t msize; + HT_ITEM *item; + + if (handle == 0 || key == 0) + return (NULL); + + if (handle->ht_flags & HTHF_FIXED_KEY) { + key_len = handle->ht_key_size; + } else { + key_len = strlen(key); + + if (key_len > handle->ht_key_size) + return (NULL); + + /* Include the null terminator */ + ++key_len; + } + + msize = key_len + sizeof (HT_ITEM); + + if ((item = malloc(msize)) == 0) + return (NULL); + + item->hi_key = (char *)item + sizeof (HT_ITEM); + (void) memcpy(item->hi_key, key, key_len); + item->hi_data = (void *)data; + item->hi_flags = 0; + + h_index = handle->ht_hash(handle, key); + + /* + * Add to the front of the list. + */ + item->hi_next = handle->ht_table[h_index].he_head; + handle->ht_table[h_index].he_head = item; + + handle->ht_table[h_index].he_count++; + handle->ht_total_items++; + handle->ht_sequence++; + + return (item); +} + + +/* + * ht_replace_item + * + * Replace an item in a hash table. The item associated with key is removed + * using ht_remove_item and a new item is added using ht_add_item. We rely + * on parameter validation in ht_remove_item and ht_add_item. + * + * The table sequence number may be modified here. + */ +HT_ITEM * +ht_replace_item(HT_HANDLE *handle, const char *key, const void *data) +{ + (void) ht_remove_item(handle, key); + + return (ht_add_item(handle, key, data)); +} + + +/* + * ht_remove_item + * + * Remove an item from a hash table. If there are duplicate keys, then the + * first key found will be deleted. Note that the data pointer is never + * dereferenced. If a callback is installed, it will be invoked and the + * return value will be null. Otherwise, the data pointer supplied by the + * application will be returned. If there is an error, a null pointer will + * be returned. + * + * The table sequence number may be modified here. + */ +void * +ht_remove_item(HT_HANDLE *handle, const char *key) +{ + size_t h_index; + HT_ITEM *cur, *prev; + int key_len; + void *data = 0; + + if (handle == 0 || key == 0) + return (NULL); + + if ((handle->ht_flags & HTHF_FIXED_KEY) == 0) + key_len = strlen(key) + 1; + else + key_len = handle->ht_key_size; + + h_index = handle->ht_hash(handle, key); + + cur = handle->ht_table[h_index].he_head; + prev = 0; + + while (cur) { + if (!(cur->hi_flags & HTIF_MARKED_DELETED) && + (handle->ht_cmp(cur->hi_key, key, key_len) == 0)) { + /* found key */ + if (prev == 0) + handle->ht_table[h_index].he_head = + cur->hi_next; + else + prev->hi_next = cur->hi_next; + + if (handle->ht_callback) + handle->ht_callback(cur); + else + data = cur->hi_data; + + /* + * Since the key and the item were allocated as + * a single chunk, we only need one free here. + */ + free(cur); + + handle->ht_table[h_index].he_count--; + handle->ht_total_items--; + handle->ht_sequence++; + break; + } + + prev = cur; + cur = cur->hi_next; + } + + return (data); +} + +/* + * ht_find_item + * + * Find an item in a hash table. If there are duplicate keys then the + * first item found (which will be the last one added) will be returned. + * + * Returns a pointer to an item. Otherwise returns a null pointer to + * indicate an error or that the key didn't match anything in the table. + */ +HT_ITEM * +ht_find_item(HT_HANDLE *handle, const char *key) +{ + size_t h_index; + HT_ITEM *cur; + int key_len; + + if (handle == 0 || key == 0) + return (NULL); + + if ((handle->ht_flags & HTHF_FIXED_KEY) == 0) + key_len = strlen(key) + 1; + else + key_len = handle->ht_key_size; + + h_index = handle->ht_hash(handle, key); + cur = handle->ht_table[h_index].he_head; + + while (cur) { + if (!(cur->hi_flags & HTIF_MARKED_DELETED) && + (handle->ht_cmp(cur->hi_key, key, key_len) == 0)) + return (cur); + + cur = cur->hi_next; + } + + return (NULL); +} + + +/* + * ht_register_callback + * + * Register an application callback function that can be used to process + * an item when it is removed from the table, i.e. free any memory + * allocated for that data item. + * + * The previous callback function pointer, which may be null, before + * registering the new one. This provides the caller with the option to + * restore a previous callback as required. + */ +HT_CALLBACK +ht_register_callback(HT_HANDLE *handle, HT_CALLBACK callback) +{ + HT_CALLBACK old_callback; + + if (handle == 0) + return (NULL); + + old_callback = handle->ht_callback; + handle->ht_callback = callback; + + return (old_callback); +} + + +/* + * ht_clean_table + * + * This function removes all the items that are marked for deletion. Note + * that this will invoke the callback, if one has been installed. If this + * call is used, the callback mechanism is the only way for an application + * to free the item data if it was dynamically allocated. + * + * The table sequence number may be modified here. + * + * Returns 0 if the handle is valid; otherwise returns -1. + */ +size_t +ht_clean_table(HT_HANDLE *handle) +{ + size_t i; + HT_ITEM *cur, *prev; + + if (handle == 0) + return ((size_t)-1); + + for (i = 0; i < handle->ht_table_size; i++) { + cur = handle->ht_table[i].he_head; + prev = 0; + + while (cur) { + if (cur->hi_flags & HTIF_MARKED_DELETED) { + /* + * We have a marked item: remove it. + */ + if (prev == 0) + handle->ht_table[i].he_head = + cur->hi_next; + else + prev->hi_next = cur->hi_next; + + if (handle->ht_callback) + handle->ht_callback(cur); + + /* + * Since the key and the item were allocated as + * a single chunk, we only need one free here. + */ + free(cur); + + handle->ht_table[i].he_count--; + handle->ht_sequence++; + + if (prev == 0) + cur = handle->ht_table[i].he_head; + else + cur = prev->hi_next; + continue; + } + + prev = cur; + cur = cur->hi_next; + } + } + + return (0); +} + + +/* + * ht_mark_delete + * + * This function marks an item for deletion, which may be useful when + * using findfirst/findnext to avoid modifying the table during the + * table scan. Marked items can be removed later using ht_clean_table. + */ +void +ht_mark_delete(HT_HANDLE *handle, HT_ITEM *item) +{ + if (handle && item) { + item->hi_flags |= HTIF_MARKED_DELETED; + handle->ht_total_items--; + } +} + +/* + * ht_clear_delete + * + * This function clear an item from marked for deletion list. + */ +void +ht_clear_delete(HT_HANDLE *handle, HT_ITEM *item) +{ + if (handle && item) { + item->hi_flags &= ~HTIF_MARKED_DELETED; + handle->ht_total_items++; + } +} + +/* + * ht_bucket_search + * + * Returns first item which is not marked as deleted + * in the specified bucket by 'head' + */ +static HT_ITEM * +ht_bucket_search(HT_ITEM *head) +{ + HT_ITEM *item = head; + while ((item != 0) && (item->hi_flags & HTIF_MARKED_DELETED)) + item = item->hi_next; + + return (item); +} + +/* + * ht_findfirst + * + * This function is used to begin an iteration through the hash table. + * The iterator is initialized and the first item in the table (as + * determined by the hash algorithm) is returned. The current sequence + * number is stored in the iterator to determine whether or not the + * the table has changed between calls. If the table is empty, a null + * pointer is returned. + */ +HT_ITEM * +ht_findfirst(HT_HANDLE *handle, HT_ITERATOR *iterator) +{ + HT_ITEM *item; + size_t h_index; + + if (handle == 0 || iterator == 0 || handle->ht_total_items == 0) + return (NULL); + + (void) memset(iterator, 0, sizeof (HT_ITERATOR)); + iterator->hti_handle = handle; + iterator->hti_sequence = handle->ht_sequence; + + for (h_index = 0; h_index < handle->ht_table_size; ++h_index) { + item = ht_bucket_search(handle->ht_table[h_index].he_head); + if (item != 0) { + iterator->hti_index = h_index; + iterator->hti_item = item; + return (item); + } + } + + return (NULL); +} + +/* + * ht_findnext + * + * Find the next item in the table for the given iterator. Iterators must + * be initialized by ht_findfirst, which will also return the first item + * in the table. If an item is available, a pointer to it is returned. + * Otherwise a null pointer is returned. A null pointer may indicate: + * + * - an invalid iterator (i.e. ht_findfirst has not been called) + * - the table has changed since the previous findfirst/findnext + * - the entire table has been traversed + * + * The caller can use ht_get_total_items to determine whether or not all + * of the items in the table have been visited. + */ +HT_ITEM * +ht_findnext(HT_ITERATOR *iterator) +{ + HT_HANDLE *handle; + HT_ITEM *item; + size_t total; + size_t index; + + if (iterator == 0 || iterator->hti_handle == 0 || + iterator->hti_sequence == 0) { + /* Invalid iterator */ + return (NULL); + } + + handle = iterator->hti_handle; + + if (iterator->hti_item == 0 || + iterator->hti_sequence != handle->ht_sequence) { + /* + * No more items or the table has changed + * since the last call. + */ + return (NULL); + } + + /* + * Check for another item in the current bucket. + */ + item = ht_bucket_search(iterator->hti_item->hi_next); + if (item != 0) { + iterator->hti_item = item; + return (item); + } + + /* + * Nothing else in the current bucket. Look for another + * bucket with something in it and return the head item. + */ + total = handle->ht_table_size; + for (index = iterator->hti_index + 1; index < total; ++index) { + item = ht_bucket_search(handle->ht_table[index].he_head); + if (item != 0) { + iterator->hti_index = index; + iterator->hti_item = item; + return (item); + } + } + + iterator->hti_index = 0; + iterator->hti_item = 0; + iterator->hti_sequence = 0; + return (NULL); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_idmap.c b/usr/src/lib/smbsrv/libsmb/common/smb_idmap.c new file mode 100644 index 0000000000..f410fe3237 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_idmap.c @@ -0,0 +1,382 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <strings.h> +#include <smbsrv/libsmb.h> + +static idmap_handle_t *idmap_clnt_hdl = NULL; +static int smb_idmap_batch_binsid(smb_idmap_batch_t *sib); + +/* + * smb_idmap_start + * + * This function initializes the idmap client handle. It should be called + * at startup. + */ +int +smb_idmap_start(void) +{ + idmap_stat stat; + + if (idmap_clnt_hdl) + return (0); + + stat = idmap_init(&idmap_clnt_hdl); + if (stat < 0) { + syslog(LOG_ERR, "smb_idmap_start: idmap_init failed (%s)", + idmap_stat2string(NULL, stat)); + return (-1); + } + + return (0); +} + +/* + * smb_idmap_stop + * + * This function destroys the idmap client handle. It should be called + * prior to exiting the SMB daemon. + */ +void +smb_idmap_stop(void) +{ + if (idmap_clnt_hdl) { + (void) idmap_fini(idmap_clnt_hdl); + idmap_clnt_hdl = NULL; + } +} + +/* + * smb_idmap_restart + * + * This function should be called when the idmap client handle + * becomes invalid. + */ +int +smb_idmap_restart(void) +{ + smb_idmap_stop(); + if (smb_idmap_start() != 0) { + syslog(LOG_ERR, "smb_idmap_restart: smb_idmap_start failed"); + return (-1); + } + + return (0); +} + +/* + * smb_idmap_getsid + * + * Tries to get a mapping for the given uid/gid + */ +idmap_stat +smb_idmap_getsid(uid_t id, int idtype, nt_sid_t **sid) +{ + smb_idmap_batch_t sib; + idmap_stat stat; + + stat = smb_idmap_batch_create(&sib, 1, SMB_IDMAP_ID2SID); + if (stat != IDMAP_SUCCESS) + return (stat); + + stat = smb_idmap_batch_getsid(sib.sib_idmaph, &sib.sib_maps[0], + id, idtype); + + if (stat != IDMAP_SUCCESS) { + smb_idmap_batch_destroy(&sib); + return (stat); + } + + stat = smb_idmap_batch_getmappings(&sib); + + if (stat != IDMAP_SUCCESS) { + smb_idmap_batch_destroy(&sib); + return (stat); + } + + *sid = nt_sid_dup(sib.sib_maps[0].sim_sid); + + smb_idmap_batch_destroy(&sib); + + return (IDMAP_SUCCESS); +} + +/* + * smb_idmap_batch_create + * + * Creates and initializes the context for batch ID mapping. + */ +idmap_stat +smb_idmap_batch_create(smb_idmap_batch_t *sib, uint16_t nmap, int flags) +{ + idmap_stat stat; + + if (!sib) + return (IDMAP_ERR_ARG); + + bzero(sib, sizeof (smb_idmap_batch_t)); + stat = idmap_get_create(idmap_clnt_hdl, &sib->sib_idmaph); + if (stat != IDMAP_SUCCESS) + return (stat); + + sib->sib_flags = flags; + sib->sib_nmap = nmap; + sib->sib_size = nmap * sizeof (smb_idmap_t); + sib->sib_maps = malloc(sib->sib_size); + if (!sib->sib_maps) + return (IDMAP_ERR_MEMORY); + + bzero(sib->sib_maps, sib->sib_size); + return (IDMAP_SUCCESS); +} + +/* + * smb_idmap_batch_destroy + * + * Frees the batch ID mapping context. + */ +void +smb_idmap_batch_destroy(smb_idmap_batch_t *sib) +{ + nt_sid_t *sid; + char *domsid; + int i; + + if (!sib) + return; + + if (sib->sib_idmaph) { + idmap_get_destroy(sib->sib_idmaph); + sib->sib_idmaph = NULL; + } + + if (!sib->sib_maps) + return; + + switch (sib->sib_flags) { + case SMB_IDMAP_SID2ID: + /* + * SIDs are allocated only when mapping + * UID/GID to SIDs + */ + for (i = 0; i < sib->sib_nmap; i++) { + sid = sib->sib_maps[i].sim_sid; + if (sid) + free(sid); + } + break; + case SMB_IDMAP_ID2SID: + /* + * SID prefixes are allocated only when mapping + * SIDs to UID/GID + */ + for (i = 0; i < sib->sib_nmap; i++) { + domsid = sib->sib_maps[i].sim_domsid; + if (domsid) + free(domsid); + } + break; + default: + break; + } + + if (sib->sib_size && sib->sib_maps) { + free(sib->sib_maps); + sib->sib_maps = NULL; + } +} + +/* + * smb_idmap_batch_getid + * + * Queue a request to map the given SID to a UID or GID. + * + * sim->sim_id should point to variable that's supposed to + * hold the returned UID/GID. This needs to be setup by caller + * of this function. + * If requested ID type is known, it's passed as 'idtype', + * if it's unknown it'll be returned in sim->sim_idtype. + */ +idmap_stat +smb_idmap_batch_getid(idmap_get_handle_t *idmaph, smb_idmap_t *sim, + nt_sid_t *sid, int idtype) +{ + nt_sid_t *tmpsid; + idmap_stat stat; + int flag = 0; + + if (!idmaph || !sim || !sid) + return (IDMAP_ERR_ARG); + + tmpsid = nt_sid_dup(sid); + if (!tmpsid) + return (IDMAP_ERR_MEMORY); + + if (nt_sid_split(tmpsid, &sim->sim_rid) != 0) { + free(tmpsid); + return (IDMAP_ERR_ARG); + } + + sim->sim_domsid = nt_sid_format(tmpsid); + free(tmpsid); + + switch (idtype) { + case SMB_IDMAP_USER: + stat = idmap_get_uidbysid(idmaph, sim->sim_domsid, + sim->sim_rid, flag, sim->sim_id, &sim->sim_stat); + break; + + case SMB_IDMAP_GROUP: + stat = idmap_get_gidbysid(idmaph, sim->sim_domsid, + sim->sim_rid, flag, sim->sim_id, &sim->sim_stat); + break; + + case SMB_IDMAP_UNKNOWN: + stat = idmap_get_pidbysid(idmaph, sim->sim_domsid, + sim->sim_rid, flag, sim->sim_id, &sim->sim_idtype, + &sim->sim_stat); + break; + + default: + return (IDMAP_ERR_ARG); + } + + return (stat); +} + +/* + * smb_idmap_batch_getsid + * + * Queue a request to map the given UID/GID to a SID. + * + * sim->sim_domsid and sim->sim_rid will contain the mapping + * result upon successful process of the batched request. + */ +idmap_stat +smb_idmap_batch_getsid(idmap_get_handle_t *idmaph, smb_idmap_t *sim, + uid_t id, int idtype) +{ + idmap_stat stat; + int flag = 0; + + if (!idmaph || !sim) + return (IDMAP_ERR_ARG); + + switch (idtype) { + case SMB_IDMAP_USER: + stat = idmap_get_sidbyuid(idmaph, id, flag, + &sim->sim_domsid, &sim->sim_rid, &sim->sim_stat); + break; + + case SMB_IDMAP_GROUP: + stat = idmap_get_sidbygid(idmaph, id, flag, + &sim->sim_domsid, &sim->sim_rid, &sim->sim_stat); + break; + + case SMB_IDMAP_EVERYONE: + /* Everyone S-1-1-0 */ + sim->sim_domsid = "S-1-1"; + sim->sim_rid = 0; + sim->sim_stat = IDMAP_SUCCESS; + stat = IDMAP_SUCCESS; + break; + + default: + return (IDMAP_ERR_ARG); + } + + return (stat); +} + +/* + * smb_idmap_batch_getmappings + * + * trigger ID mapping service to get the mappings for queued + * requests. + * + * Checks the result of all the queued requests. + */ +idmap_stat +smb_idmap_batch_getmappings(smb_idmap_batch_t *sib) +{ + idmap_stat stat = IDMAP_SUCCESS; + int i; + + stat = idmap_get_mappings(sib->sib_idmaph); + if (stat != IDMAP_SUCCESS) { + return (stat); + } + + /* + * Check the status for all the queued requests + */ + for (i = 0; i < sib->sib_nmap; i++) { + if (sib->sib_maps[i].sim_stat != IDMAP_SUCCESS) { + return (sib->sib_maps[i].sim_stat); + } + } + + if (smb_idmap_batch_binsid(sib) != 0) { + stat = IDMAP_ERR_OTHER; + } + + return (stat); +} + +/* + * smb_idmap_batch_binsid + * + * Convert sidrids to binary sids + * + * Returns 0 if successful and non-zero upon failure. + */ +static int +smb_idmap_batch_binsid(smb_idmap_batch_t *sib) +{ + nt_sid_t *sid; + smb_idmap_t *sim; + int i; + + if (sib->sib_flags & SMB_IDMAP_SID2ID) + /* This operation is not required */ + return (0); + + sim = sib->sib_maps; + for (i = 0; i < sib->sib_nmap; sim++, i++) { + if (sim->sim_domsid == NULL) + return (-1); + + sid = nt_sid_strtosid(sim->sim_domsid); + if (sid == NULL) + return (-1); + + sim->sim_sid = nt_sid_splice(sid, sim->sim_rid); + free(sid); + } + + return (0); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_info.c b/usr/src/lib/smbsrv/libsmb/common/smb_info.c new file mode 100644 index 0000000000..24d7b1b3d1 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_info.c @@ -0,0 +1,382 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <stdarg.h> +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#include <synch.h> +#include <syslog.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include <net/if.h> +#include <netdb.h> +#include <sys/sockio.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/netbios.h> +#include <smbsrv/libsmb.h> + +static smb_ntdomain_t smbpdc_cache; +static mutex_t smbpdc_mtx; +static cond_t smbpdc_cv; + +extern int getdomainname(char *, int); + +uint32_t +smb_get_security_mode() +{ + uint32_t mode; + + smb_config_rdlock(); + mode = smb_config_get_secmode(); + smb_config_unlock(); + + return (mode); +} + +/* + * smb_purge_domain_info + * + * Clean out the environment in preparation for joining a domain. + * This ensures that we don't have any old information lying around. + */ +void +smb_purge_domain_info(void) +{ + smb_config_wrlock(); + (void) smb_config_set(SMB_CI_DOMAIN_NAME, 0); + (void) smb_config_set(SMB_CI_DOMAIN_SID, 0); + (void) smb_config_set(SMB_CI_DOMAIN_MEMB, 0); + smb_config_unlock(); +} + +int +smb_is_domain_member(void) +{ + int is_memb; + + smb_config_rdlock(); + is_memb = smb_config_getyorn(SMB_CI_DOMAIN_MEMB); + smb_config_unlock(); + + return (is_memb); +} + +uint8_t +smb_get_fg_flag(void) +{ + uint8_t run_fg; + + smb_config_rdlock(); + run_fg = smb_config_get_fg_flag(); + smb_config_unlock(); + + return (run_fg); +} + +void +smb_set_domain_member(int set) +{ + char *member; + + smb_config_wrlock(); + member = (set) ? "true" : "false"; + (void) smb_config_set(SMB_CI_DOMAIN_MEMB, member); + smb_config_unlock(); +} + +/* + * smb_getdomaininfo + * + * Returns a pointer to the cached domain data. The caller can specify + * whether or not he is prepared to wait if the cache is not yet valid + * and for how long. The specified timeout is in seconds. + */ +smb_ntdomain_t * +smb_getdomaininfo(uint32_t timeout) +{ + timestruc_t to; + int err; + + if (timeout != 0) { + (void) mutex_lock(&smbpdc_mtx); + while (smbpdc_cache.ipaddr == 0) { + to.tv_sec = timeout; + to.tv_nsec = 0; + err = cond_reltimedwait(&smbpdc_cv, &smbpdc_mtx, &to); + if (err == ETIME) + break; + } + (void) mutex_unlock(&smbpdc_mtx); + } + + if (smbpdc_cache.ipaddr != 0) + return (&smbpdc_cache); + else + return (0); +} + +void +smb_logdomaininfo(smb_ntdomain_t *di) +{ + char ipstr[16]; + + (void) inet_ntop(AF_INET, (const void *)&di->ipaddr, ipstr, + sizeof (ipstr)); + syslog(LOG_DEBUG, "smbd: %s (%s:%s)", di->domain, di->server, ipstr); +} + +/* + * smb_setdomaininfo + * + * Set the information for the specified domain. If the information is + * non-null, the notification event is raised to wakeup any threads + * blocking on the cache. + */ +void +smb_setdomaininfo(char *domain, char *server, uint32_t ipaddr) +{ + char *p; + + bzero(&smbpdc_cache, sizeof (smb_ntdomain_t)); + + if (domain && server && ipaddr) { + (void) strlcpy(smbpdc_cache.domain, domain, SMB_PI_MAX_DOMAIN); + (void) strlcpy(smbpdc_cache.server, server, SMB_PI_MAX_DOMAIN); + + /* + * Remove DNS domain name extension + * to avoid confusing NetBIOS. + */ + if ((p = strchr(smbpdc_cache.domain, '.')) != 0) + *p = '\0'; + + if ((p = strchr(smbpdc_cache.server, '.')) != 0) + *p = '\0'; + + (void) mutex_lock(&smbpdc_mtx); + smbpdc_cache.ipaddr = ipaddr; + (void) cond_broadcast(&smbpdc_cv); + (void) mutex_unlock(&smbpdc_mtx); + } +} + +void +smb_load_kconfig(smb_kmod_cfg_t *kcfg) +{ + smb_config_rdlock(); + bzero(kcfg, sizeof (smb_kmod_cfg_t)); + + kcfg->skc_maxbufsize = smb_config_getnum(SMB_CI_MAX_BUFSIZE); + kcfg->skc_maxworkers = smb_config_getnum(SMB_CI_MAX_WORKERS); + kcfg->skc_keepalive = smb_config_getnum(SMB_CI_KEEPALIVE); + if ((kcfg->skc_keepalive != 0) && + (kcfg->skc_keepalive < SMB_PI_KEEP_ALIVE_MIN)) + kcfg->skc_keepalive = SMB_PI_KEEP_ALIVE_MIN; + kcfg->skc_restrict_anon = smb_config_getyorn(SMB_CI_RESTRICT_ANON); + + kcfg->skc_signing_enable = smb_config_getyorn(SMB_CI_SIGNING_ENABLE); + kcfg->skc_signing_required = smb_config_getyorn(SMB_CI_SIGNING_REQD); + kcfg->skc_signing_check = smb_config_getyorn(SMB_CI_SIGNING_CHECK); + + kcfg->skc_oplock_enable = smb_config_getyorn(SMB_CI_OPLOCK_ENABLE); + kcfg->skc_oplock_timeout = smb_config_getnum(SMB_CI_OPLOCK_TIMEOUT); + + kcfg->skc_flush_required = smb_config_getyorn(SMB_CI_FLUSH_REQUIRED); + kcfg->skc_sync_enable = smb_config_getyorn(SMB_CI_SYNC_ENABLE); + kcfg->skc_dirsymlink_enable = + !smb_config_getyorn(SMB_CI_DIRSYMLINK_DISABLE); + kcfg->skc_announce_quota = smb_config_getyorn(SMB_CI_ANNONCE_QUOTA); + kcfg->skc_announce_quota = smb_config_getyorn(SMB_CI_ANNONCE_QUOTA); + + kcfg->skc_secmode = smb_config_get_secmode(); + kcfg->skc_lmlevel = smb_config_getnum(SMB_CI_LM_LEVEL); + kcfg->skc_maxconnections = smb_config_getnum(SMB_CI_MAX_CONNECTIONS); + + (void) strlcpy(kcfg->skc_resource_domain, + smb_config_getstr(SMB_CI_DOMAIN_NAME), + sizeof (kcfg->skc_resource_domain)); + + (void) smb_gethostname(kcfg->skc_hostname, + sizeof (kcfg->skc_hostname), 1); + + (void) strlcpy(kcfg->skc_system_comment, + smb_config_getstr(SMB_CI_SYS_CMNT), + sizeof (kcfg->skc_system_comment)); + + smb_config_unlock(); +} + +/* + * Get the current system NetBIOS name. The hostname is truncated at + * the first `.` or 15 bytes, whichever occurs first, and converted + * to uppercase (by smb_gethostname). Text that appears after the + * first '.' is considered to be part of the NetBIOS scope. + * + * Returns 0 on success, otherwise -1 to indicate an error. + */ +int +smb_getnetbiosname(char *buf, size_t buflen) +{ + if (smb_gethostname(buf, buflen, 1) != 0) + return (-1); + + if (buflen >= NETBIOS_NAME_SZ) + buf[NETBIOS_NAME_SZ - 1] = '\0'; + + return (0); +} + +/* + * Get the current system node name. The returned name is guaranteed + * to be null-terminated (gethostname may not null terminate the name). + * If the hostname has been fully-qualified for some reason, the domain + * part will be removed. If the caller would like the name in upper + * case, it is folded to uppercase. + * + * If gethostname fails, the returned buffer will contain an empty + * string. + */ +int +smb_gethostname(char *buf, size_t buflen, int upcase) +{ + char *p; + + if (buf == NULL || buflen == 0) + return (-1); + + if (gethostname(buf, buflen) != 0) { + *buf = '\0'; + return (-1); + } + + buf[buflen - 1] = '\0'; + + if ((p = strchr(buf, '.')) != NULL) + *p = '\0'; + + if (upcase) + (void) utf8_strupr(buf); + + return (0); +} + +/* + * The ADS domain is often the same as the DNS domain but they can be + * different - one might be a sub-domain of the other. + * + * If an ADS domain name has been configured, return it. Otherwise, + * return the DNS domain name. + * + * If getdomainname fails, the returned buffer will contain an empty + * string. + */ +int +smb_getdomainname(char *buf, size_t buflen) +{ + char *domain; + + if (buf == NULL || buflen == 0) + return (-1); + + smb_config_rdlock(); + + domain = smb_config_getstr(SMB_CI_ADS_DOMAIN); + if ((domain != NULL) && (*domain != '\0')) { + (void) strlcpy(buf, domain, buflen); + smb_config_unlock(); + return (0); + } + + smb_config_unlock(); + + if (getdomainname(buf, buflen) != 0) { + *buf = '\0'; + return (-1); + } + + return (0); +} + +/* + * Obtain the fully-qualified name for this machine. If the + * hostname is fully-qualified, accept it. Otherwise, try to + * find an appropriate domain name to append to the hostname. + */ +int +smb_getfqhostname(char *buf, size_t buflen) +{ + char hostname[MAXHOSTNAMELEN]; + char domain[MAXHOSTNAMELEN]; + + hostname[0] = '\0'; + domain[0] = '\0'; + + if (smb_gethostname(hostname, MAXHOSTNAMELEN, 0) != 0) + return (-1); + + if (smb_getdomainname(domain, MAXHOSTNAMELEN) != 0) + return (-1); + + if (hostname[0] == '\0') + return (-1); + + if (domain[0] == '\0') { + (void) strlcpy(buf, hostname, buflen); + return (0); + } + + (void) snprintf(buf, buflen, "%s.%s", hostname, domain); + return (0); +} + +/* + * Temporary fbt for dtrace until user space sdt enabled. + */ +void +smb_tracef(const char *fmt, ...) +{ + va_list ap; + char buf[128]; + + va_start(ap, fmt); + (void) vsnprintf(buf, 128, fmt, ap); + va_end(ap); + + smb_trace(buf); +} + +/* + * Temporary fbt for dtrace until user space sdt enabled. + */ +void +smb_trace(const char *s) +{ + syslog(LOG_DEBUG, "%s", s); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_mac.c b/usr/src/lib/smbsrv/libsmb/common/smb_mac.c new file mode 100644 index 0000000000..57fb74530c --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_mac.c @@ -0,0 +1,207 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * SMB MAC Signing support. + */ + +#include <strings.h> +#include <security/cryptoki.h> +#include <security/pkcs11.h> + +#include <smbsrv/libsmb.h> + +#include <smbsrv/smb.h> + +/* + * smb_mac_init + * + * Calculates the MAC key using the specified user session + * key (NTLM or NTLMv2). + * + * Returns SMBAUTH_SUCCESS if key generation was successful, + * SMBAUTH_FAILURE if not. + */ +int +smb_mac_init(smb_sign_ctx_t *sign_ctx, smb_auth_info_t *auth) +{ + unsigned char S16[SMBAUTH_SESSION_KEY_SZ]; + + if (smb_auth_gen_session_key(auth, S16) != SMBAUTH_SUCCESS) + return (SMBAUTH_FAILURE); + bcopy(S16, sign_ctx->ssc_mackey, SMBAUTH_SESSION_KEY_SZ); + bcopy(auth->cs, &(sign_ctx->ssc_mackey[SMBAUTH_SESSION_KEY_SZ]), + auth->cs_len); + sign_ctx->ssc_keylen = SMBAUTH_SESSION_KEY_SZ + auth->cs_len; + return (SMBAUTH_SUCCESS); +} + +/* + * smb_mac_calc + * + * Calculates MAC signature for the given buffer and returns + * it in the mac_sign parameter. + * + * The MAC signature is calculated as follows: + * + * data = concat(MAC_Key, MAC_Key_Len, SMB_Msg, SMB_Msg_Len); + * hash = MD5(data); + * MAC = head(hash, 8); + * + * The tricky part is that a sequence number should be used + * in calculation instead of the signature field in the + * SMB header. + * + * Returns SMBAUTH_SUCCESS if cryptology framework use was successful, + * SMBAUTH_FAILURE if not. + */ +int +smb_mac_calc(smb_sign_ctx_t *sign_ctx, const unsigned char *buf, + size_t buf_len, unsigned char *mac_sign) +{ + CK_RV rv; + CK_MECHANISM mechanism; + CK_SESSION_HANDLE hSession; + unsigned long diglen = MD_DIGEST_LEN; + int rc = SMBAUTH_FAILURE; + + int offset_end_of_sig = (SMB_SIG_OFFS + SMB_SIG_SIZE); + unsigned char seq_buf[SMB_SIG_SIZE]; + unsigned char mac[16]; + + /* + * put seq_num into the first 4 bytes and + * zero out the next 4 bytes + */ + bcopy(&sign_ctx->ssc_seqnum, seq_buf, 4); + bzero(seq_buf + 4, 4); + + mechanism.mechanism = CKM_MD5; + mechanism.pParameter = 0; + mechanism.ulParameterLen = 0; + + rv = SUNW_C_GetMechSession(mechanism.mechanism, &hSession); + if (rv != CKR_OK) + return (SMBAUTH_FAILURE); + + /* Initialize the digest operation in the session */ + rv = C_DigestInit(hSession, &mechanism); + if (rv != CKR_OK) + goto smbmacdone; + + /* init with the MAC key */ + rv = C_DigestUpdate(hSession, sign_ctx->ssc_mackey, + sign_ctx->ssc_keylen); + if (rv != CKR_OK) + goto smbmacdone; + + /* copy in SMB packet info till signature field */ + rv = C_DigestUpdate(hSession, (CK_BYTE_PTR)buf, SMB_SIG_OFFS); + if (rv != CKR_OK) + goto smbmacdone; + + /* copy in the seq_buf instead of the signature */ + rv = C_DigestUpdate(hSession, seq_buf, sizeof (seq_buf)); + if (rv != CKR_OK) + goto smbmacdone; + + /* copy in the rest of the packet, skipping the signature */ + rv = C_DigestUpdate(hSession, (CK_BYTE_PTR)buf + offset_end_of_sig, + buf_len - offset_end_of_sig); + if (rv != CKR_OK) + goto smbmacdone; + + rv = C_DigestFinal(hSession, mac, &diglen); + if (rv != CKR_OK) + goto smbmacdone; + + bcopy(mac, mac_sign, SMB_SIG_SIZE); + rc = SMBAUTH_SUCCESS; + +smbmacdone: + (void) C_CloseSession(hSession); + return (rc); +} + +/* + * smb_mac_chk + * + * Calculates MAC signature for the given buffer + * and compares it to the signature in the given context. + * Return 1 if the signature are match, otherwise, return (0); + */ +int +smb_mac_chk(smb_sign_ctx_t *sign_ctx, + const unsigned char *buf, size_t buf_len) +{ + unsigned char mac_sign[SMB_SIG_SIZE]; + + /* calculate mac signature */ + if (smb_mac_calc(sign_ctx, buf, buf_len, mac_sign) != SMBAUTH_SUCCESS) + return (0); + + /* compare the signatures */ + if (memcmp(sign_ctx->ssc_sign, mac_sign, SMB_SIG_SIZE) == 0) + return (1); + + return (0); +} + +/* + * smb_mac_sign + * + * Calculates MAC signature for the given buffer, + * and write it to the buffer's signature field. + * + * Returns SMBAUTH_SUCCESS if cryptology framework use was successful, + * SMBAUTH_FAILURE if not. + */ +int +smb_mac_sign(smb_sign_ctx_t *sign_ctx, unsigned char *buf, size_t buf_len) +{ + unsigned char mac_sign[SMB_SIG_SIZE]; + + /* calculate mac signature */ + if (smb_mac_calc(sign_ctx, buf, buf_len, mac_sign) != SMBAUTH_SUCCESS) + return (SMBAUTH_FAILURE); + + /* put mac signature in the header's signature field */ + (void) memcpy(buf + SMB_SIG_OFFS, mac_sign, SMB_SIG_SIZE); + return (SMBAUTH_SUCCESS); +} + +void +smb_mac_inc_seqnum(smb_sign_ctx_t *sign_ctx) +{ + sign_ctx->ssc_seqnum++; +} + +void +smb_mac_dec_seqnum(smb_sign_ctx_t *sign_ctx) +{ + sign_ctx->ssc_seqnum--; +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_privilege.c b/usr/src/lib/smbsrv/libsmb/common/smb_privilege.c new file mode 100644 index 0000000000..ce7cef198a --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_privilege.c @@ -0,0 +1,361 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module provides the interface to the built-in privilege names + * and id's. NT privileges are known on the network using strings. Each + * system assigns locally unique identifiers (LUID) for use within the + * system. Each built-in privilege also has a display-name, which is a + * short description of the privilege. The functions here provide an + * interface to map between LUIDs, names and display names. + */ + +#include <string.h> +#include <syslog.h> + +#include <smbsrv/string.h> +#include <smbsrv/libsmb.h> +#include <smbsrv/smb_privilege.h> + +#define SMB_PRIV_MIN 2 +#define SMB_PRIV_MAX 24 + +static char *smb_priv_getname(uint32_t id); + +/* + * Table of built-in privilege id's, names and display strings. This + * table matches the response from an NT4.0 PDC LSARPC service. + * Requests for values 0 and 1 return STATUS_NO_SUCH_PRIVILEGE. + * + * SE_UNSOLICITED_INPUT_NAME/SeUnsolicitedInputPrivilege is defined in + * winnt.h but doesn't appear in the list reported by the NT4.0 LSA. + */ +static smb_privinfo_t priv_table[] = { + { 0, "", "", 0 }, + { 1, "", "", 0 }, + { 2, SE_CREATE_TOKEN_NAME, "Create a token object", 0 }, + { 3, SE_ASSIGNPRIMARYTOKEN_NAME, "Replace a process level token", 0 }, + { 4, SE_LOCK_MEMORY_NAME, "Lock pages in memory", 0 }, + { 5, SE_INCREASE_QUOTA_NAME, "Increase quotas", 0 }, + { 6, SE_MACHINE_ACCOUNT_NAME, "Add workstations to domain", 0 }, + { 7, SE_TCB_NAME, "Act as part of the operating system", 0 }, + { 8, SE_SECURITY_NAME, "Manage auditing and security log", 0 }, + { 9, SE_TAKE_OWNERSHIP_NAME, + "Take ownership of files or other objects", PF_PRESENTABLE }, + { 10, SE_LOAD_DRIVER_NAME, "Load and unload device drivers", 0 }, + { 11, SE_SYSTEM_PROFILE_NAME, "Profile system performance", 0 }, + { 12, SE_SYSTEMTIME_NAME, "Change the system time", 0 }, + { 13, SE_PROF_SINGLE_PROCESS_NAME, "Profile single process", 0 }, + { 14, SE_INC_BASE_PRIORITY_NAME, "Increase scheduling priority", 0 }, + { 15, SE_CREATE_PAGEFILE_NAME, "Create a pagefile", 0 }, + { 16, SE_CREATE_PERMANENT_NAME, "Create permanent shared objects", 0 }, + { 17, SE_BACKUP_NAME, "Back up files and directories", + PF_PRESENTABLE }, + { 18, SE_RESTORE_NAME, "Restore files and directories", + PF_PRESENTABLE }, + { 19, SE_SHUTDOWN_NAME, "Shut down the system", 0 }, + { 20, SE_DEBUG_NAME, "Debug programs", 0 }, + { 21, SE_AUDIT_NAME, "Generate security audits", 0 }, + { 22, SE_SYSTEM_ENVIRONMENT_NAME, + "Modify firmware environment values", 0 }, + { 23, SE_CHANGE_NOTIFY_NAME, "Bypass traverse checking", 0 }, + { 24, SE_REMOTE_SHUTDOWN_NAME, + "Force shutdown from a remote system", 0 } +}; + +/* + * smb_priv_presentable_num + * + * Returns number of presentable privileges + */ +int +smb_priv_presentable_num() +{ + int i, num; + + num = 0; + for (i = SMB_PRIV_MIN; i <= SMB_PRIV_MAX; i++) + if (priv_table[i].flags == PF_PRESENTABLE) + num++; + + return (num); +} + +/* + * smb_priv_presentable_ids + * + * Returns IDs of presentable privileges + * Returns 0 in case of invalid parameter and 1 on success. + */ +int +smb_priv_presentable_ids(uint32_t *ids, int num) +{ + int i, j; + + if (ids == NULL || num <= 0) + return (0); + + for (i = SMB_PRIV_MIN, j = 0; i <= SMB_PRIV_MAX; i++) + if (priv_table[i].flags == PF_PRESENTABLE) + ids[j++] = priv_table[i].id; + + return (1); +} + +/* + * smb_priv_getbyvalue + * + * Return the privilege info for the specified id (low part of the LUID). + * Returns a null pointer if id is out-of-range. + */ +smb_privinfo_t * +smb_priv_getbyvalue(uint32_t id) +{ + if (id < SMB_PRIV_MIN || id > SMB_PRIV_MAX) + return (0); + + return (&priv_table[id]); +} + + +/* + * smb_priv_getbyname + * + * Return the privilege info for the specified name. Returns a null + * pointer if we can't find a matching name in the table. + */ +smb_privinfo_t * +smb_priv_getbyname(char *name) +{ + smb_privinfo_t *entry; + int i; + + if (name == 0) + return (0); + + for (i = SMB_PRIV_MIN; i <= SMB_PRIV_MAX; ++i) { + entry = &priv_table[i]; + + if (utf8_strcasecmp(name, entry->name) == 0) + return (entry); + } + + return (0); +} + +/* + * smb_privset_size + * + * Returns the memory block size needed to keep a complete + * set of privileges in a smb_privset_t structure. + */ +int +smb_privset_size() +{ + int pcnt = SMB_PRIV_MAX - SMB_PRIV_MIN + 1; + + return (2 * sizeof (uint32_t) + + pcnt * sizeof (smb_luid_attrs_t)); +} + +/* + * smb_privset_validate + * + * Validates the given privilege set structure + * Returns 1 if the structure is Ok, otherwise returns 0. + */ +int +smb_privset_validate(smb_privset_t *privset) +{ + int count; + uint32_t i; + + if (privset == 0) { + return (0); + } + + count = SMB_PRIV_MAX - SMB_PRIV_MIN + 1; + + if (privset->priv_cnt != count) { + return (0); + } + + for (i = 0; i < count; i++) { + if (privset->priv[i].luid.hi_part != 0) { + return (0); + } + + if (privset->priv[i].luid.lo_part != + i + SMB_PRIV_MIN) { + return (0); + } + } + + return (1); +} + +/* + * smb_privset_init + * + * initialize all privileges in disable state. + */ +void +smb_privset_init(smb_privset_t *privset) +{ + int count; + uint32_t i; + + if (privset == 0) + return; + + count = SMB_PRIV_MAX - SMB_PRIV_MIN + 1; + + privset->priv_cnt = count; + privset->control = 0; + for (i = 0; i < count; i++) { + privset->priv[i].luid.hi_part = 0; + privset->priv[i].luid.lo_part = i + SMB_PRIV_MIN; + privset->priv[i].attrs = 0; + } +} + +/* + * smb_privset_new + * + * Allocate memory and initialize all privileges in disable state. + * Returns pointer to allocated space or NULL if there is not + * enough memory. + */ +smb_privset_t * +smb_privset_new() +{ + smb_privset_t *privset; + + privset = malloc(smb_privset_size()); + if (privset == NULL) + return (NULL); + + smb_privset_init(privset); + + return (privset); +} + +/* + * smb_privset_copy + * + * Copy privleges information specified by 'src' to the + * buffer specified by dst. + */ +void +smb_privset_copy(smb_privset_t *dst, smb_privset_t *src) +{ + if (src == 0 || dst == 0) + return; + + (void) memcpy(dst, src, smb_privset_size()); +} + +/* + * smb_privset_free + * + * This will free the memory allocated by the 'privset'. + */ +void +smb_privset_free(smb_privset_t *privset) +{ + free(privset); +} + +void +smb_privset_enable(smb_privset_t *privset, uint32_t id) +{ + int i; + + if (privset == NULL) + return; + + for (i = 0; i < privset->priv_cnt; i++) { + if (privset->priv[i].luid.lo_part == id) + privset->priv[i].attrs = SE_PRIVILEGE_ENABLED; + } +} + +void +smb_privset_log(smb_privset_t *privset) +{ + smb_luid_t *luid; + int i, ecnt; + + if (privset == NULL) + return; + + for (i = 0, ecnt = 0; i < privset->priv_cnt; ++i) { + if (privset->priv[i].attrs != 0) { + ecnt++; + } + } + + syslog(LOG_DEBUG, " Privilege Count: %d (Enable=%d)", + privset->priv_cnt, ecnt); + + for (i = 0; i < privset->priv_cnt; ++i) { + if (privset->priv[i].attrs != 0) { + luid = &privset->priv[i].luid; + syslog(LOG_DEBUG, " %s", + smb_priv_getname(luid->lo_part)); + } + } +} + +int +smb_privset_query(smb_privset_t *privset, uint32_t id) +{ + int i; + + if (privset == NULL) + return (0); + + for (i = 0; privset->priv_cnt; i++) { + if (privset->priv[i].luid.lo_part == id) { + if (privset->priv[i].attrs == SE_PRIVILEGE_ENABLED) + return (1); + else + return (0); + } + } + + return (0); +} + +static char * +smb_priv_getname(uint32_t id) +{ + if (id < SMB_PRIV_MIN || id > SMB_PRIV_MAX) + return ("Unknown Privilege"); + + return (priv_table[id].name); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c b/usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c new file mode 100644 index 0000000000..10026d7418 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_pwdutil.c @@ -0,0 +1,529 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdlib.h> +#include <unistd.h> +#include <limits.h> +#include <strings.h> +#include <synch.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <thread.h> +#include <pwd.h> +#include <smbsrv/libsmb.h> + +#define SMB_PASSWD "/var/smb/smbpasswd" +#define SMB_OPASSWD "/var/smb/osmbpasswd" +#define SMB_PASSTEMP "/var/smb/ptmp" +#define SMB_PASSLCK "/var/smb/.pwd.lock" + +#define SMB_PWD_DISABLE "*DIS*" +#define SMB_PWD_BUFSIZE 256 + +#define S_WAITTIME 15 + +typedef enum { + SMB_PWD_NAME = 0, + SMB_PWD_UID, + SMB_PWD_LMHASH, + SMB_PWD_NTHASH, + SMB_PWD_NARG +} smb_pwdarg_t; + +static struct flock flock = { + 0, /* l_type */ + 0, /* l_whence */ + 0, /* l_start */ + 0, /* l_len */ + 0, /* l_sysid */ + 0 /* l_pid */ + }; + +static pid_t lck_pid = 0; /* process's pid at last lock */ +static thread_t lck_tid = 0; /* thread that holds the lock */ +static int fildes = -1; +static mutex_t lck_lock = DEFAULTMUTEX; + +typedef struct smb_pwbuf { + char *pw_name; + smb_passwd_t *pw_pwd; +} smb_pwbuf_t; + +static int smb_pwd_lock(void); +static int smb_pwd_unlock(void); +static int smb_pwd_flck(void); +static int smb_pwd_fulck(void); + +static smb_pwbuf_t *smb_pwd_fgetent(FILE *, smb_pwbuf_t *, char *, size_t); +static int smb_pwd_fputent(FILE *, smb_pwbuf_t *); +static int smb_pwd_chgpwent(smb_passwd_t *, const char *, int); +static int smb_pwd_update(const char *, const char *, int); + +/* + * smb_pwd_get + * + * Returns a smb password structure for the given user name. + * smbpw is a pointer to a buffer allocated by the caller. + * + * Returns NULL upon failure. + */ +smb_passwd_t * +smb_pwd_getpasswd(const char *name, smb_passwd_t *smbpw) +{ + char buf[SMB_PWD_BUFSIZE]; + boolean_t found = B_FALSE; + smb_pwbuf_t pwbuf; + int err; + FILE *fp; + + err = smb_pwd_lock(); + if (err != SMB_PWE_SUCCESS) + return (NULL); + + if ((fp = fopen(SMB_PASSWD, "rF")) == NULL) { + (void) smb_pwd_unlock(); + return (NULL); + } + + pwbuf.pw_name = NULL; + pwbuf.pw_pwd = smbpw; + + while (smb_pwd_fgetent(fp, &pwbuf, buf, sizeof (buf)) != NULL) { + if (strcmp(name, pwbuf.pw_name) == 0) { + if ((smbpw->pw_flags & (SMB_PWF_LM | SMB_PWF_NT))) + found = B_TRUE; + break; + } + } + + (void) fclose(fp); + (void) smb_pwd_unlock(); + + if (!found) { + bzero(smbpw, sizeof (smb_passwd_t)); + return (NULL); + } + + return (smbpw); +} + +/* + * smb_pwd_set + * + * Update/add the given user to the smbpasswd file. + */ +int +smb_pwd_setpasswd(const char *name, const char *password) +{ + return (smb_pwd_update(name, password, 0)); +} + +/* + * smb_pwd_setcntl + * + * Change the account state. This can be making the account + * disable/enable or removing its LM hash. + */ +int +smb_pwd_setcntl(const char *name, int control) +{ + if (control == 0) + return (SMB_PWE_SUCCESS); + + return (smb_pwd_update(name, NULL, control)); +} + +static int +smb_pwd_update(const char *name, const char *password, int control) +{ + struct stat64 stbuf; + FILE *src, *dst; + int tempfd; + char buf[SMB_PWD_BUFSIZE]; + int err = SMB_PWE_SUCCESS; + smb_pwbuf_t pwbuf; + smb_passwd_t smbpw; + boolean_t newent = B_TRUE; + boolean_t user_disable = B_FALSE; + char uxbuf[1024]; + struct passwd uxpw; + int lm_level; + char *lm_str; + + err = smb_pwd_lock(); + if (err != SMB_PWE_SUCCESS) + return (err); + + if (stat64(SMB_PASSWD, &stbuf) < 0) { + err = SMB_PWE_STAT_FAILED; + goto passwd_exit; + } + + if ((tempfd = open(SMB_PASSTEMP, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) { + err = SMB_PWE_OPEN_FAILED; + goto passwd_exit; + } + + if ((dst = fdopen(tempfd, "wF")) == NULL) { + err = SMB_PWE_OPEN_FAILED; + goto passwd_exit; + } + + if ((src = fopen(SMB_PASSWD, "rF")) == NULL) { + err = SMB_PWE_OPEN_FAILED; + (void) fclose(dst); + (void) unlink(SMB_PASSTEMP); + goto passwd_exit; + } + + lm_str = smb_config_getenv(SMB_CI_LM_LEVEL); + if (lm_str) { + lm_level = strtoul(lm_str, 0, 10); + free(lm_str); + } else { + lm_level = 4; + } + + if (lm_level >= 4) + control |= SMB_PWC_NOLM; + + /* + * copy old password entries to temporary file while replacing + * the entry that matches "name" + */ + pwbuf.pw_name = NULL; + pwbuf.pw_pwd = &smbpw; + + while (smb_pwd_fgetent(src, &pwbuf, buf, sizeof (buf)) != NULL) { + if (strcmp(pwbuf.pw_name, name) == 0) { + err = smb_pwd_chgpwent(&smbpw, password, control); + if (err == SMB_PWE_USER_DISABLE) + user_disable = B_TRUE; + err = smb_pwd_fputent(dst, &pwbuf); + newent = B_FALSE; + } else { + err = smb_pwd_fputent(dst, &pwbuf); + } + + if (err != SMB_PWE_SUCCESS) { + (void) fclose(src); + (void) fclose(dst); + goto passwd_exit; + } + } + + if (newent) { + if (getpwnam_r(name, &uxpw, uxbuf, sizeof (uxbuf))) { + pwbuf.pw_name = uxpw.pw_name; + smbpw.pw_flags = 0; + smbpw.pw_uid = uxpw.pw_uid; + (void) smb_pwd_chgpwent(&smbpw, password, control); + err = smb_pwd_fputent(dst, &pwbuf); + } else { + err = SMB_PWE_USER_UNKNOWN; + } + + if (err != SMB_PWE_SUCCESS) { + (void) fclose(src); + (void) fclose(dst); + goto passwd_exit; + } + } + + (void) fclose(src); + if (fclose(dst) != 0) { + err = SMB_PWE_CLOSE_FAILED; + goto passwd_exit; /* Don't trust the temporary file */ + } + + /* Rename temp to passwd */ + if (unlink(SMB_OPASSWD) && access(SMB_OPASSWD, 0) == 0) { + err = SMB_PWE_UPDATE_FAILED; + (void) unlink(SMB_PASSTEMP); + goto passwd_exit; + } + + if (link(SMB_PASSWD, SMB_OPASSWD) == -1) { + err = SMB_PWE_UPDATE_FAILED; + (void) unlink(SMB_PASSTEMP); + goto passwd_exit; + } + + if (rename(SMB_PASSTEMP, SMB_PASSWD) == -1) { + err = SMB_PWE_UPDATE_FAILED; + (void) unlink(SMB_PASSTEMP); + goto passwd_exit; + } + + (void) chmod(SMB_PASSWD, 0400); + +passwd_exit: + (void) smb_pwd_unlock(); + if ((err == SMB_PWE_SUCCESS) && user_disable) + err = SMB_PWE_USER_DISABLE; + + return (err); +} + +/* + * smb_getpwent + * + * Parse the buffer in the passed pwbuf and fill in the + * smb password structure to point to the parsed information. + * The entry format is: + * + * <user-name>:<user-id>:<LM hash>:<NTLM hash> + * + * Returns a pointer to the password structure on success, + * otherwise returns NULL. + */ +static smb_pwbuf_t * +smb_pwd_fgetent(FILE *fp, smb_pwbuf_t *pwbuf, char *buf, size_t bufsize) +{ + char *argv[SMB_PWD_NARG]; + smb_passwd_t *pw; + smb_pwdarg_t i; + int lm_len, nt_len; + + if (fgets(buf, bufsize, fp) == NULL) + return (NULL); + (void) trim_whitespace(buf); + + for (i = 0; i < SMB_PWD_NARG; ++i) { + if ((argv[i] = strsep((char **)&buf, ":")) == 0) { + return (NULL); + } + } + + if ((*argv[SMB_PWD_NAME] == '\0') || (*argv[SMB_PWD_UID] == '\0')) + return (NULL); + + pwbuf->pw_name = argv[SMB_PWD_NAME]; + pw = pwbuf->pw_pwd; + bzero(pw, sizeof (smb_passwd_t)); + pw->pw_uid = strtoul(argv[SMB_PWD_UID], 0, 10); + + if (strcmp(argv[SMB_PWD_LMHASH], SMB_PWD_DISABLE) == 0) { + pw->pw_flags |= SMB_PWF_DISABLE; + (void) strcpy((char *)pw->pw_lmhash, SMB_PWD_DISABLE); + (void) strcpy((char *)pw->pw_nthash, SMB_PWD_DISABLE); + return (pwbuf); + } + + lm_len = strlen(argv[SMB_PWD_LMHASH]); + if (lm_len == SMBAUTH_HEXHASH_SZ) { + (void) hextobin(argv[SMB_PWD_LMHASH], SMBAUTH_HEXHASH_SZ, + (char *)pw->pw_lmhash, SMBAUTH_HASH_SZ); + + pw->pw_flags |= SMB_PWF_LM; + } else if (lm_len != 0) { + return (NULL); + } + + nt_len = strlen(argv[SMB_PWD_NTHASH]); + if (nt_len == SMBAUTH_HEXHASH_SZ) { + (void) hextobin(argv[SMB_PWD_NTHASH], SMBAUTH_HEXHASH_SZ, + (char *)pw->pw_nthash, SMBAUTH_HASH_SZ); + + pw->pw_flags |= SMB_PWF_NT; + } else if (nt_len != 0) { + return (NULL); + } + + return (pwbuf); +} + +static int +smb_pwd_chgpwent(smb_passwd_t *smbpw, const char *password, int control) +{ + if (control & SMB_PWC_DISABLE) { + smbpw->pw_flags |= SMB_PWF_DISABLE; + (void) strcpy((char *)smbpw->pw_lmhash, SMB_PWD_DISABLE); + (void) strcpy((char *)smbpw->pw_nthash, SMB_PWD_DISABLE); + smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT); + return (SMB_PWE_SUCCESS); + } else if ((control & SMB_PWC_ENABLE) && + (smbpw->pw_flags & SMB_PWF_DISABLE)) { + *smbpw->pw_lmhash = '\0'; + *smbpw->pw_nthash = '\0'; + smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT); + return (SMB_PWE_SUCCESS); + } + + /* No password update if account is disabled */ + if (smbpw->pw_flags & SMB_PWF_DISABLE) + return (SMB_PWE_USER_DISABLE); + + if (control & SMB_PWC_NOLM) { + smbpw->pw_flags &= ~SMB_PWF_LM; + *smbpw->pw_lmhash = '\0'; + } else { + smbpw->pw_flags |= SMB_PWF_LM; + (void) smb_auth_lm_hash((char *)password, smbpw->pw_lmhash); + } + + smbpw->pw_flags |= SMB_PWF_NT; + (void) smb_auth_ntlm_hash((char *)password, smbpw->pw_nthash); + return (SMB_PWE_SUCCESS); +} + +/* + * smb_putpwent + * + * Creates LM and NTLM hash from the given plain text password + * and write them along with user's name and Id to the smbpasswd + * file. + */ +static int +smb_pwd_fputent(FILE *fp, smb_pwbuf_t *pwbuf) +{ + smb_passwd_t *pw = pwbuf->pw_pwd; + char hex_nthash[SMBAUTH_HEXHASH_SZ+1]; + char hex_lmhash[SMBAUTH_HEXHASH_SZ+1]; + int rc; + + if ((pw->pw_flags & SMB_PWF_LM) == SMB_PWF_LM) { + (void) bintohex((char *)pw->pw_lmhash, SMBAUTH_HASH_SZ, + hex_lmhash, SMBAUTH_HEXHASH_SZ); + hex_lmhash[SMBAUTH_HEXHASH_SZ] = '\0'; + } else { + (void) strcpy(hex_lmhash, (char *)pw->pw_lmhash); + } + + if ((pw->pw_flags & SMB_PWF_NT) == SMB_PWF_NT) { + (void) bintohex((char *)pw->pw_nthash, SMBAUTH_HASH_SZ, + hex_nthash, SMBAUTH_HEXHASH_SZ); + hex_nthash[SMBAUTH_HEXHASH_SZ] = '\0'; + } else { + (void) strcpy(hex_nthash, (char *)pw->pw_nthash); + } + + rc = fprintf(fp, "%s:%d:%s:%s\n", pwbuf->pw_name, pw->pw_uid, + hex_lmhash, hex_nthash); + + if (rc <= 0) + return (SMB_PWE_WRITE_FAILED); + + return (SMB_PWE_SUCCESS); +} + +static int +smb_pwd_lock(void) +{ + int res; + + if (smb_pwd_flck()) { + switch (errno) { + case EINTR: + res = SMB_PWE_BUSY; + break; + case EACCES: + res = SMB_PWE_DENIED; + break; + case 0: + res = SMB_PWE_SUCCESS; + break; + } + } else + res = SMB_PWE_SUCCESS; + + return (res); +} + +static int +smb_pwd_unlock(void) +{ + if (smb_pwd_fulck()) + return (SMB_PWE_SYSTEM_ERROR); + + return (SMB_PWE_SUCCESS); +} + +static int +smb_pwd_flck(void) +{ + int seconds = 0; + + (void) mutex_lock(&lck_lock); + for (;;) { + if (lck_pid != 0 && lck_pid != getpid()) { + /* somebody forked */ + lck_pid = 0; + lck_tid = 0; + } + + if (lck_tid == 0) { + if ((fildes = creat(SMB_PASSLCK, 0600)) == -1) + break; + flock.l_type = F_WRLCK; + if (fcntl(fildes, F_SETLK, &flock) != -1) { + lck_pid = getpid(); + lck_tid = thr_self(); + (void) mutex_unlock(&lck_lock); + return (0); + } + (void) close(fildes); + fildes = -1; + } + + if (seconds++ >= S_WAITTIME) { + /* + * For compatibility with the past, pretend + * that we were interrupted by SIGALRM. + */ + errno = EINTR; + break; + } + + (void) mutex_unlock(&lck_lock); + (void) sleep(1); + (void) mutex_lock(&lck_lock); + } + (void) mutex_unlock(&lck_lock); + + return (-1); +} + +static int +smb_pwd_fulck(void) +{ + (void) mutex_lock(&lck_lock); + if (lck_tid == thr_self() && fildes >= 0) { + flock.l_type = F_UNLCK; + (void) fcntl(fildes, F_SETLK, &flock); + (void) close(fildes); + fildes = -1; + lck_pid = 0; + lck_tid = 0; + (void) mutex_unlock(&lck_lock); + return (0); + } + (void) mutex_unlock(&lck_lock); + return (-1); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_scfutil.c b/usr/src/lib/smbsrv/libsmb/common/smb_scfutil.c new file mode 100644 index 0000000000..1f5083f448 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_scfutil.c @@ -0,0 +1,1035 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* helper functions for using libscf with CIFS */ + +#include <libscf.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> +#include <errno.h> +#include <libintl.h> +#include <assert.h> +#include <strings.h> + +#include <uuid/uuid.h> +#include <sys/param.h> + +#include <smbsrv/alloc.h> + +#include <smbsrv/libsmb.h> + +/* + * smb_smf_scf_log_error(msg) + * Logs error messages from scf API's + */ +static void +smb_smf_scf_log_error(char *msg) +{ + if (!msg) { + syslog(LOG_ERR, " SMBD SMF problem: %s\n", + scf_strerror(scf_error())); + } else { /*LINTED E_SEC_PRINTF_E_VAR_FMT*/ + syslog(LOG_ERR, msg, scf_strerror(scf_error())); + } +} + +/* + * Check if instance with given name exists for a service. + * Returns 0 is instance exist + */ +int +smb_smf_instance_exists(smb_scfhandle_t *handle, char *inst_name) +{ + int ret = SMBD_SMF_OK; + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + handle->scf_instance = scf_instance_create(handle->scf_handle); + if (scf_service_get_instance(handle->scf_service, inst_name, + handle->scf_instance) != SCF_SUCCESS) + ret = SMBD_SMF_SYSTEM_ERR; + + scf_instance_destroy(handle->scf_instance); + handle->scf_instance = NULL; + return (ret); +} + +/* + * Create a service instance. returns 0 if successful. + * If instance already exists enable it. + */ +int +smb_smf_instance_create(smb_scfhandle_t *handle, char *serv_prefix, + char *inst_name) +{ + char *instance; + int ret = SMBD_SMF_OK; + int sz; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + if (!serv_prefix || !inst_name) + return (SMBD_SMF_SYSTEM_ERR); + + sz = strlen(serv_prefix) + strlen(inst_name) + 2; + instance = malloc(sz); + if (!instance) + return (SMBD_SMF_NO_MEMORY); + + (void) snprintf(instance, sz, "%s:%s", serv_prefix, inst_name); + handle->scf_instance = scf_instance_create(handle->scf_handle); + if (scf_service_get_instance(handle->scf_service, inst_name, + handle->scf_instance) != SCF_SUCCESS) { + if (scf_service_add_instance(handle->scf_service, + inst_name, handle->scf_instance) == SCF_SUCCESS) { + if (smf_enable_instance(instance, 0)) + ret = SMBD_SMF_SYSTEM_ERR; + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + if (smf_enable_instance(instance, 0)) + ret = SMBD_SMF_SYSTEM_ERR; + } + free(instance); + return (ret); +} + +/* + * Delete a specified instance. Return SMBD_SMF_OK for success. + */ +int +smb_smf_instance_delete(smb_scfhandle_t *handle, char *inst_name) +{ + int ret = SMBD_SMF_OK; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + handle->scf_instance = scf_instance_create(handle->scf_handle); + if (scf_service_get_instance(handle->scf_service, inst_name, + handle->scf_instance) == SCF_SUCCESS) { + if (scf_instance_delete(handle->scf_instance) == SCF_SUCCESS) { + return (ret); + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + smb_smf_scf_log_error(NULL); + ret = SMBD_SMF_SYSTEM_ERR; + } + return (ret); +} + +/* + * smb_smf_create_service_pgroup(handle, pgroup) + * + * create a new property group at service level. + */ +int +smb_smf_create_service_pgroup(smb_scfhandle_t *handle, char *pgroup) +{ + int ret = SMBD_SMF_OK; + int err; + + if (handle == NULL) { + return (SMBD_SMF_SYSTEM_ERR); + } + + /* + * only create a handle if it doesn't exist. It is ok to exist + * since the pg handle will be set as a side effect. + */ + if (handle->scf_pg == NULL) + handle->scf_pg = scf_pg_create(handle->scf_handle); + + /* + * if the pgroup exists, we are done. If it doesn't, then we + * need to actually add one to the service instance. + */ + if (scf_service_get_pg(handle->scf_service, + pgroup, handle->scf_pg) != 0) { + /* doesn't exist so create one */ + if (scf_service_add_pg(handle->scf_service, pgroup, + SCF_GROUP_APPLICATION, 0, handle->scf_pg) != 0) { + err = scf_error(); + if (err != SCF_ERROR_NONE) + smb_smf_scf_log_error(NULL); + switch (err) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SMBD_SMF_NO_PERMISSION; + break; + default: + ret = SMBD_SMF_SYSTEM_ERR; + break; + } + } + } + return (ret); +} + +/* + * smb_smf_create_instance_pgroup(handle, pgroup) + * + * create a new property group at instance level. + */ +int +smb_smf_create_instance_pgroup(smb_scfhandle_t *handle, char *pgroup) +{ + int ret = SMBD_SMF_OK; + int err; + + if (handle == NULL) { + return (SMBD_SMF_SYSTEM_ERR); + } + + /* + * only create a handle if it doesn't exist. It is ok to exist + * since the pg handle will be set as a side effect. + */ + if (handle->scf_pg == NULL) + handle->scf_pg = scf_pg_create(handle->scf_handle); + + /* + * if the pgroup exists, we are done. If it doesn't, then we + * need to actually add one to the service instance. + */ + if (scf_instance_get_pg(handle->scf_instance, + pgroup, handle->scf_pg) != 0) { + /* doesn't exist so create one */ + if (scf_instance_add_pg(handle->scf_instance, pgroup, + SCF_GROUP_FRAMEWORK, 0, handle->scf_pg) != 0) { + err = scf_error(); + if (err != SCF_ERROR_NONE) + smb_smf_scf_log_error(NULL); + switch (err) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SMBD_SMF_NO_PERMISSION; + break; + default: + ret = SMBD_SMF_SYSTEM_ERR; + break; + } + } + } + return (ret); +} + +/* + * smb_smf_delete_service_pgroup(handle, pgroup) + * + * remove the property group from the current service. + * but only if it actually exists. + */ +int +smb_smf_delete_service_pgroup(smb_scfhandle_t *handle, char *pgroup) +{ + int ret = SMBD_SMF_OK; + int err; + + if (handle == NULL) { + return (SMBD_SMF_SYSTEM_ERR); + } + + /* + * only create a handle if it doesn't exist. It is ok to exist + * since the pg handle will be set as a side effect. + */ + if (handle->scf_pg == NULL) + handle->scf_pg = scf_pg_create(handle->scf_handle); + + /* + * only delete if it does exist. + */ + if (scf_service_get_pg(handle->scf_service, + pgroup, handle->scf_pg) == 0) { + /* does exist so delete it */ + if (scf_pg_delete(handle->scf_pg) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + err = scf_error(); + if (err != SCF_ERROR_NONE) { + smb_smf_scf_log_error("SMF delpg " + "problem: %s\n"); + } + } + } else { + err = scf_error(); + if (err != SCF_ERROR_NONE) + smb_smf_scf_log_error("SMF getpg problem: %s\n"); + ret = SMBD_SMF_SYSTEM_ERR; + } + if (ret == SMBD_SMF_SYSTEM_ERR && + scf_error() == SCF_ERROR_PERMISSION_DENIED) { + ret = SMBD_SMF_NO_PERMISSION; + } + return (ret); +} + +/* + * smb_smf_delete_instance_pgroup(handle, pgroup) + * + * remove the property group from the current instance. + * but only if it actually exists. + */ +int +smb_smf_delete_instance_pgroup(smb_scfhandle_t *handle, char *pgroup) +{ + int ret = SMBD_SMF_OK; + int err; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + /* + * only create a handle if it doesn't exist. It is ok to exist + * since the pg handle will be set as a side effect. + */ + if (handle->scf_pg == NULL) + handle->scf_pg = scf_pg_create(handle->scf_handle); + + /* + * only delete if it does exist. + */ + if (scf_instance_get_pg(handle->scf_instance, + pgroup, handle->scf_pg) == 0) { + /* does exist so delete it */ + if (scf_pg_delete(handle->scf_pg) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + err = scf_error(); + if (err != SCF_ERROR_NONE) { + smb_smf_scf_log_error("SMF delpg " + "problem: %s\n"); + } + } + } else { + err = scf_error(); + if (err != SCF_ERROR_NONE) + smb_smf_scf_log_error("SMF getpg problem: %s\n"); + ret = SMBD_SMF_SYSTEM_ERR; + } + if (ret == SMBD_SMF_SYSTEM_ERR && + scf_error() == SCF_ERROR_PERMISSION_DENIED) + ret = SMBD_SMF_NO_PERMISSION; + + return (ret); +} + +/* + * Start transaction on current pg in handle. + * The pg could be service or instance level. + * Must be called after pg handle is obtained + * from create or get. + */ +int +smb_smf_start_transaction(smb_scfhandle_t *handle) +{ + int ret = SMBD_SMF_OK; + + if (!handle || (!handle->scf_pg)) + return (SMBD_SMF_SYSTEM_ERR); + + /* + * lookup the property group and create it if it doesn't already + * exist. + */ + if (handle->scf_state == SCH_STATE_INIT) { + if (ret == SMBD_SMF_OK) { + handle->scf_trans = + scf_transaction_create(handle->scf_handle); + if (handle->scf_trans != NULL) { + if (scf_transaction_start(handle->scf_trans, + handle->scf_pg) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + scf_transaction_destroy( + handle->scf_trans); + handle->scf_trans = NULL; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + } + } + if (ret == SMBD_SMF_SYSTEM_ERR && + scf_error() == SCF_ERROR_PERMISSION_DENIED) + ret = SMBD_SMF_NO_PERMISSION; + + return (ret); +} + +/* + * smb_smf_end_transaction(handle) + * + * Commit the changes that were added to the transaction in the + * handle. Do all necessary cleanup. + */ +int +smb_smf_end_transaction(smb_scfhandle_t *handle) +{ + int ret = SMBD_SMF_OK; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + if (handle->scf_trans == NULL) { + ret = SMBD_SMF_SYSTEM_ERR; + } else { + if (scf_transaction_commit(handle->scf_trans) < 0) { + ret = SMBD_SMF_SYSTEM_ERR; + smb_smf_scf_log_error("Failed to commit " + "transaction: %s"); + } + scf_transaction_destroy_children(handle->scf_trans); + scf_transaction_destroy(handle->scf_trans); + handle->scf_trans = NULL; + } + return (ret); +} + +/* + * Deletes property in current pg + */ +int +smb_smf_delete_property(smb_scfhandle_t *handle, char *propname) +{ + int ret = SMBD_SMF_OK; + scf_transaction_entry_t *entry = NULL; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + /* + * properties must be set in transactions and don't take + * effect until the transaction has been ended/committed. + */ + entry = scf_entry_create(handle->scf_handle); + if (entry != NULL) { + if (scf_transaction_property_delete(handle->scf_trans, entry, + propname) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + if (ret == SMBD_SMF_SYSTEM_ERR) { + switch (scf_error()) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SMBD_SMF_NO_PERMISSION; + break; + } + } + + /* + * cleanup if there were any errors that didn't leave these + * values where they would be cleaned up later. + */ + if ((ret != SMBD_SMF_OK) && (entry != NULL)) + scf_entry_destroy(entry); + + return (ret); +} + +/* + * Sets string property in current pg + */ +int +smb_smf_set_string_property(smb_scfhandle_t *handle, + char *propname, char *valstr) +{ + int ret = SMBD_SMF_OK; + scf_value_t *value = NULL; + scf_transaction_entry_t *entry = NULL; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + /* + * properties must be set in transactions and don't take + * effect until the transaction has been ended/committed. + */ + value = scf_value_create(handle->scf_handle); + entry = scf_entry_create(handle->scf_handle); + if (value != NULL && entry != NULL) { + if (scf_transaction_property_change(handle->scf_trans, entry, + propname, SCF_TYPE_ASTRING) == 0 || + scf_transaction_property_new(handle->scf_trans, entry, + propname, SCF_TYPE_ASTRING) == 0) { + if (scf_value_set_astring(value, valstr) == 0) { + if (scf_entry_add_value(entry, value) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + scf_value_destroy(value); + } + /* the value is in the transaction */ + value = NULL; + } else { + /* value couldn't be constructed */ + ret = SMBD_SMF_SYSTEM_ERR; + } + /* the entry is in the transaction */ + entry = NULL; + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + if (ret == SMBD_SMF_SYSTEM_ERR) { + switch (scf_error()) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SMBD_SMF_NO_PERMISSION; + break; + } + } + + /* + * cleanup if there were any errors that didn't leave these + * values where they would be cleaned up later. + */ + if (value != NULL) + scf_value_destroy(value); + if (entry != NULL) + scf_entry_destroy(entry); + return (ret); +} + +/* + * Gets string property value.upto sz size. + * Caller is responsible to have enough memory allocated. + */ +int +smb_smf_get_string_property(smb_scfhandle_t *handle, char *propname, + char *valstr, size_t sz) +{ + int ret = SMBD_SMF_OK; + scf_value_t *value; + scf_property_t *prop; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + value = scf_value_create(handle->scf_handle); + prop = scf_property_create(handle->scf_handle); + if (value && prop && + (scf_pg_get_property(handle->scf_pg, propname, prop) == 0)) { + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_astring(value, valstr, sz) < 0) { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + if (value != NULL) + scf_value_destroy(value); + if (prop != NULL) + scf_property_destroy(prop); + return (ret); +} + +/* + * Set integer value of property. + * The value is returned as int64_t value + * Caller ensures appropriate translation. + */ +int +smb_smf_set_integer_property(smb_scfhandle_t *handle, char *propname, + int64_t valint) +{ + int ret = SMBD_SMF_OK; + scf_value_t *value = NULL; + scf_transaction_entry_t *entry = NULL; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + /* + * properties must be set in transactions and don't take + * effect until the transaction has been ended/committed. + */ + value = scf_value_create(handle->scf_handle); + entry = scf_entry_create(handle->scf_handle); + if (value != NULL && entry != NULL) { + if (scf_transaction_property_change(handle->scf_trans, entry, + propname, SCF_TYPE_INTEGER) == 0 || + scf_transaction_property_new(handle->scf_trans, entry, + propname, SCF_TYPE_INTEGER) == 0) { + scf_value_set_integer(value, valint); + if (scf_entry_add_value(entry, value) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + scf_value_destroy(value); + } + /* the value is in the transaction */ + value = NULL; + } + /* the entry is in the transaction */ + entry = NULL; + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + if (ret == SMBD_SMF_SYSTEM_ERR) { + switch (scf_error()) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SMBD_SMF_NO_PERMISSION; + break; + } + } + /* + * cleanup if there were any errors that didn't leave these + * values where they would be cleaned up later. + */ + if (value != NULL) + scf_value_destroy(value); + if (entry != NULL) + scf_entry_destroy(entry); + return (ret); +} + +/* + * Gets integer property value. + * Caller is responsible to have enough memory allocated. + */ +int +smb_smf_get_integer_property(smb_scfhandle_t *handle, char *propname, + int64_t *valint) +{ + int ret = SMBD_SMF_OK; + scf_value_t *value = NULL; + scf_property_t *prop = NULL; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + value = scf_value_create(handle->scf_handle); + prop = scf_property_create(handle->scf_handle); + if ((prop) && (value) && + (scf_pg_get_property(handle->scf_pg, propname, prop) == 0)) { + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_integer(value, + valint) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + if (value != NULL) + scf_value_destroy(value); + if (prop != NULL) + scf_property_destroy(prop); + return (ret); +} + +/* + * Set boolean value of property. + * The value is returned as int64_t value + * Caller ensures appropriate translation. + */ +int +smb_smf_set_boolean_property(smb_scfhandle_t *handle, char *propname, + uint8_t valbool) +{ + int ret = SMBD_SMF_OK; + scf_value_t *value = NULL; + scf_transaction_entry_t *entry = NULL; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + /* + * properties must be set in transactions and don't take + * effect until the transaction has been ended/committed. + */ + value = scf_value_create(handle->scf_handle); + entry = scf_entry_create(handle->scf_handle); + if (value != NULL && entry != NULL) { + if (scf_transaction_property_change(handle->scf_trans, entry, + propname, SCF_TYPE_BOOLEAN) == 0 || + scf_transaction_property_new(handle->scf_trans, entry, + propname, SCF_TYPE_BOOLEAN) == 0) { + scf_value_set_boolean(value, valbool); + if (scf_entry_add_value(entry, value) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + scf_value_destroy(value); + } + /* the value is in the transaction */ + value = NULL; + } + /* the entry is in the transaction */ + entry = NULL; + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + if (ret == SMBD_SMF_SYSTEM_ERR) { + switch (scf_error()) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SMBD_SMF_NO_PERMISSION; + break; + } + } + /* + * cleanup if there were any errors that didn't leave these + * values where they would be cleaned up later. + */ + if (value != NULL) + scf_value_destroy(value); + if (entry != NULL) + scf_entry_destroy(entry); + return (ret); +} + +/* + * Gets boolean property value. + * Caller is responsible to have enough memory allocated. + */ +int +smb_smf_get_boolean_property(smb_scfhandle_t *handle, char *propname, + uint8_t *valbool) +{ + int ret = SMBD_SMF_OK; + scf_value_t *value = NULL; + scf_property_t *prop = NULL; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + value = scf_value_create(handle->scf_handle); + prop = scf_property_create(handle->scf_handle); + if ((prop) && (value) && + (scf_pg_get_property(handle->scf_pg, propname, prop) == 0)) { + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_boolean(value, + valbool) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + if (value != NULL) + scf_value_destroy(value); + if (prop != NULL) + scf_property_destroy(prop); + return (ret); +} + +/* + * Sets a blob property value. + */ +int +smb_smf_set_opaque_property(smb_scfhandle_t *handle, char *propname, + void *voidval, size_t sz) +{ + int ret = SMBD_SMF_OK; + scf_value_t *value; + scf_transaction_entry_t *entry; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + /* + * properties must be set in transactions and don't take + * effect until the transaction has been ended/committed. + */ + value = scf_value_create(handle->scf_handle); + entry = scf_entry_create(handle->scf_handle); + if (value != NULL && entry != NULL) { + if (scf_transaction_property_change(handle->scf_trans, entry, + propname, SCF_TYPE_OPAQUE) == 0 || + scf_transaction_property_new(handle->scf_trans, entry, + propname, SCF_TYPE_OPAQUE) == 0) { + if (scf_value_set_opaque(value, voidval, sz) == 0) { + if (scf_entry_add_value(entry, value) != 0) { + ret = SMBD_SMF_SYSTEM_ERR; + scf_value_destroy(value); + } + /* the value is in the transaction */ + value = NULL; + } else { + /* value couldn't be constructed */ + ret = SMBD_SMF_SYSTEM_ERR; + } + /* the entry is in the transaction */ + entry = NULL; + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + if (ret == SMBD_SMF_SYSTEM_ERR) { + switch (scf_error()) { + case SCF_ERROR_PERMISSION_DENIED: + ret = SMBD_SMF_NO_PERMISSION; + break; + } + } + /* + * cleanup if there were any errors that didn't leave these + * values where they would be cleaned up later. + */ + if (value != NULL) + scf_value_destroy(value); + if (entry != NULL) + scf_entry_destroy(entry); + return (ret); +} + +/* + * Gets a blob property value. + * Caller is responsible to have enough memory allocated. + */ +int +smb_smf_get_opaque_property(smb_scfhandle_t *handle, char *propname, + void *v, size_t sz) +{ + int ret = SMBD_SMF_OK; + scf_value_t *value = NULL; + scf_property_t *prop = NULL; + + if (handle == NULL) + return (SMBD_SMF_SYSTEM_ERR); + + value = scf_value_create(handle->scf_handle); + prop = scf_property_create(handle->scf_handle); + if ((prop) && (value) && + (scf_pg_get_property(handle->scf_pg, propname, prop) == 0)) { + if (scf_property_get_value(prop, value) == 0) { + if (scf_value_get_opaque(value, (char *)v, sz) != sz) { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + } else { + ret = SMBD_SMF_SYSTEM_ERR; + } + if (value != NULL) + scf_value_destroy(value); + if (prop != NULL) + scf_property_destroy(prop); + return (ret); +} + +/* + * Get property based on property type. Returns string value of that + * property. Only SCF_TYPE_ASTRING, SCF_TYPE_INTEGER, SCF_TYPE_BOOLEAN + * supported. + */ +int +smb_smf_get_property(smb_scfhandle_t *handle, int proptype, char *propname, + char *valstr, size_t sz) +{ + int64_t valint = 0; + uint8_t valbool = 0; + int ret = SMBD_SMF_OK; + + switch (proptype) { + case SCF_TYPE_ASTRING: + ret = smb_smf_get_string_property(handle, propname, + valstr, sz); + break; + case SCF_TYPE_INTEGER: + if ((ret = smb_smf_get_integer_property(handle, propname, + &valint)) != 0) + return (ret); + (void) snprintf(valstr, sz, "%lld", valint); + break; + case SCF_TYPE_BOOLEAN: + if ((ret = smb_smf_get_boolean_property(handle, propname, + &valbool)) != 0) + return (ret); + (void) strlcpy(valstr, (valbool ? "true" : "false"), sz); + break; + default: + return (SMBD_SMF_SYSTEM_ERR); + } + return (ret); +} + +/* + * Set property based on property type. + * Only SCF_TYPE_ASTRING, SCF_TYPE_INTEGER, SCF_TYPE_BOOLEAN supported. + */ +int +smb_smf_set_property(smb_scfhandle_t *handle, int proptype, + char *propname, char *valstr) +{ + int64_t valint = 0; + uint8_t valbool = 0; + int ret = SMBD_SMF_OK; + + switch (proptype) { + case SCF_TYPE_ASTRING: + ret = smb_smf_set_string_property(handle, propname, + valstr); + break; + case SCF_TYPE_INTEGER: + valint = strtol(valstr, 0, 10); + ret = smb_smf_set_integer_property(handle, propname, + valint); + break; + case SCF_TYPE_BOOLEAN: + if (strcasecmp(valstr, "true") == 0) + valbool = 1; + ret = smb_smf_set_boolean_property(handle, propname, valbool); + break; + default: + return (SMBD_SMF_SYSTEM_ERR); + } + return (ret); +} + +/* + * Gets an instance iterator for the service specified. + */ +smb_scfhandle_t * +smb_smf_get_iterator(char *svc_name) +{ + smb_scfhandle_t *handle = NULL; + + handle = smb_smf_scf_init(svc_name); + if (!handle) + return (NULL); + + handle->scf_inst_iter = scf_iter_create(handle->scf_handle); + if (handle->scf_inst_iter) { + if (scf_iter_service_instances(handle->scf_inst_iter, + handle->scf_service) != 0) { + smb_smf_scf_fini(handle); + handle = NULL; + } else { + handle->scf_instance = NULL; + } + } else { + smb_smf_scf_fini(handle); + handle = NULL; + } + return (handle); +} + +/* + * smb_smf_scf_init() + * + * must be called before using any of the SCF functions. + * Returns smb_scfhandle_t pointer if success. + */ +smb_scfhandle_t * +smb_smf_scf_init(char *svc_name) +{ + smb_scfhandle_t *handle; + + handle = malloc(sizeof (smb_scfhandle_t)); + if (handle != NULL) { + bzero((char *)handle, sizeof (smb_scfhandle_t)); + handle->scf_state = SCH_STATE_INITIALIZING; + handle->scf_handle = scf_handle_create(SCF_VERSION); + if (handle->scf_handle != NULL) { + if (scf_handle_bind(handle->scf_handle) == 0) { + handle->scf_scope = + scf_scope_create(handle->scf_handle); + if (scf_handle_get_local_scope( + handle->scf_handle, handle->scf_scope) != 0) + goto err; + + handle->scf_service = + scf_service_create(handle->scf_handle); + + if (scf_scope_get_service(handle->scf_scope, + svc_name, handle->scf_service) + != SCF_SUCCESS) { + goto err; + } + handle->scf_pg = + scf_pg_create(handle->scf_handle); + handle->scf_state = SCH_STATE_INIT; + } else { + goto err; + } + } else { + free(handle); + handle = NULL; + smb_smf_scf_log_error("Could not access SMF " + "repository: %s\n"); + } + } + return (handle); + + /* error handling/unwinding */ +err: + (void) smb_smf_scf_fini(handle); + (void) smb_smf_scf_log_error("SMF initialization problem: %s\n"); + return (NULL); +} + +/* + * smb_smf_scf_fini(handle) + * + * must be called when done. Called with the handle allocated in + * smb_smf_scf_init(), it cleans up the state and frees any SCF resources + * still in use. + */ +void +smb_smf_scf_fini(smb_scfhandle_t *handle) +{ + if (handle != NULL) { + int unbind = 0; + scf_iter_destroy(handle->scf_pg_iter); + handle->scf_pg_iter = NULL; + + scf_iter_destroy(handle->scf_inst_iter); + handle->scf_inst_iter = NULL; + + unbind = 1; + scf_scope_destroy(handle->scf_scope); + handle->scf_scope = NULL; + + scf_instance_destroy(handle->scf_instance); + handle->scf_instance = NULL; + + scf_service_destroy(handle->scf_service); + handle->scf_service = NULL; + + scf_pg_destroy(handle->scf_pg); + handle->scf_pg = NULL; + + handle->scf_state = SCH_STATE_UNINIT; + if (unbind) + (void) scf_handle_unbind(handle->scf_handle); + scf_handle_destroy(handle->scf_handle); + handle->scf_handle = NULL; + + free(handle); + } +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_util.c b/usr/src/lib/smbsrv/libsmb/common/smb_util.c new file mode 100644 index 0000000000..bce8efee8e --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_util.c @@ -0,0 +1,336 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <pthread.h> +#include <syslog.h> +#include <sys/varargs.h> +#include <sys/types.h> +#include <smbsrv/string.h> + +#define C2H(c) "0123456789ABCDEF"[(c)] +#define H2C(c) (((c) >= '0' && (c) <= '9') ? ((c) - '0') : \ + ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) : \ + ((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) : \ + '\0') +#define DEFAULT_SBOX_SIZE 256 + +/* + * + * hexdump + * + * Simple hex dump display function. Displays nbytes of buffer in hex and + * printable format. Non-printing characters are shown as '.'. It is safe + * to pass a null pointer. Each line begins with the offset. If nbytes is + * 0, the line will be blank except for the offset. Example output: + * + * 00000000 54 68 69 73 20 69 73 20 61 20 70 72 6F 67 72 61 This is a progra + * 00000010 6D 20 74 65 73 74 2E 00 m test.. + * + */ +void +hexdump_offset(unsigned char *buffer, int nbytes, unsigned long *start, int log) +{ + static char *hex = "0123456789ABCDEF"; + int i, count; + int offset; + unsigned char *p; + char ascbuf[64]; + char hexbuf[64]; + char *ap = ascbuf; + char *hp = hexbuf; + + if ((p = buffer) == 0) { + if (log) + syslog(LOG_DEBUG, "hexdump: (null)"); + else + (void) printf("hexdump: (null)\n"); + return; + } + + offset = *start; + + *ap = '\0'; + *hp = '\0'; + count = 0; + + for (i = 0; i < nbytes; ++i) { + if (i && (i % 16) == 0) { + if (log) + syslog(LOG_DEBUG, + "%06X %s %s", offset, hexbuf, ascbuf); + else + (void) printf("%06X %s %s\n", + offset, hexbuf, ascbuf); + ap = ascbuf; + hp = hexbuf; + count = 0; + offset += 16; + } + + ap += sprintf(ap, "%c", + (*p >= 0x20 && *p < 0x7F) ? *p : '.'); + hp += sprintf(hp, " %c%c", + hex[(*p >> 4) & 0x0F], hex[(*p & 0x0F)]); + ++p; + ++count; + } + + if (count) { + if (log) + syslog(LOG_DEBUG, + "%06X %-48s %s", offset, hexbuf, ascbuf); + else + (void) printf("%06X %-48s %s\n", + offset, hexbuf, ascbuf); + + offset += count; + } + + *start = offset; +} + +void +hexdump(unsigned char *buffer, int nbytes) +{ + unsigned long start = 0; + + hexdump_offset(buffer, nbytes, &start, 1); +} + +/* + * bintohex + * + * Converts the given binary data (srcbuf) to + * its equivalent hex chars (hexbuf). + * + * hexlen should be at least twice as srclen. + * if hexbuf is not big enough returns 0. + * otherwise returns number of valid chars in + * hexbuf which is srclen * 2. + */ +size_t +bintohex(const char *srcbuf, size_t srclen, + char *hexbuf, size_t hexlen) +{ + size_t outlen; + char c; + + outlen = srclen << 1; + + if (hexlen < outlen) + return (0); + + while (srclen-- > 0) { + c = *srcbuf++; + *hexbuf++ = C2H(c & 0xF); + *hexbuf++ = C2H((c >> 4) & 0xF); + } + + return (outlen); +} + +/* + * hextobin + * + * Converts hex to binary. + * + * Assuming hexbuf only contains hex digits (chars) + * this function convert every two bytes of hexbuf + * to one byte and put it in dstbuf. + * + * hexlen should be an even number. + * dstlen should be at least half of hexlen. + * + * Returns 0 if sizes are not correct, otherwise + * returns the number of converted bytes in dstbuf + * which is half of hexlen. + */ +size_t +hextobin(const char *hexbuf, size_t hexlen, + char *dstbuf, size_t dstlen) +{ + size_t outlen; + + if ((hexlen % 2) != 0) + return (0); + + outlen = hexlen >> 1; + if (dstlen < outlen) + return (0); + + while (hexlen > 0) { + *dstbuf = H2C(*hexbuf) & 0x0F; + hexbuf++; + *dstbuf++ |= (H2C(*hexbuf) << 4) & 0xF0; + hexbuf++; + + hexlen -= 2; + } + + return (outlen); +} + +/* + * trim_whitespace + * + * Trim leading and trailing whitespace chars (as defined by isspace) + * from a buffer. Example; if the input buffer contained " text ", + * it will contain "text", when we return. We assume that the buffer + * contains a null terminated string. A pointer to the buffer is + * returned. + */ +char * +trim_whitespace(char *buf) +{ + char *p = buf; + char *q = buf; + + if (buf == 0) + return (0); + + while (*p && isspace(*p)) + ++p; + + while ((*q = *p++) != 0) + ++q; + + if (q != buf) { + while ((--q, isspace(*q)) != 0) + *q = '\0'; + } + + return (buf); +} + +/* + * randomize + * + * Randomize the contents of the specified buffer. + */ +void +randomize(char *data, unsigned len) +{ + unsigned dwlen = len / 4; + unsigned remlen = len % 4; + unsigned tmp; + unsigned i; /*LINTED E_BAD_PTR_CAST_ALIGN*/ + unsigned *p = (unsigned *)data; + + for (i = 0; i < dwlen; ++i) + *p++ = random(); + + if (remlen) { + tmp = random(); + (void) memcpy(p, &tmp, remlen); + } +} + +/* + * This is the hash mechanism used to encrypt passwords for commands like + * SamrSetUserInformation. It uses a 256 byte s-box. + */ +void +rand_hash( + unsigned char *data, + size_t datalen, + unsigned char *key, + size_t keylen) +{ + unsigned char sbox[DEFAULT_SBOX_SIZE]; + unsigned char tmp; + unsigned char index_i = 0; + unsigned char index_j = 0; + unsigned char j = 0; + int i; + + for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) + sbox[i] = (unsigned char)i; + + for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) { + j += (sbox[i] + key[i % keylen]); + + tmp = sbox[i]; + sbox[i] = sbox[j]; + sbox[j] = tmp; + } + + for (i = 0; i < datalen; ++i) { + index_i++; + index_j += sbox[index_i]; + + tmp = sbox[index_i]; + sbox[index_i] = sbox[index_j]; + sbox[index_j] = tmp; + + tmp = sbox[index_i] + sbox[index_j]; + data[i] = data[i] ^ sbox[tmp]; + } +} + +/* + * strsep + * + * The strsep() function locates, in the string referenced by *stringp, the + * first occurrence of any character in the string delim (or the terminating + * `\0' character) and replaces it with a `\0'. The location of the next + * character after the delimiter character (or NULL, if the end of the + * string was reached) is stored in *stringp. The original value of + * *stringp is returned. + * + * If *stringp is initially NULL, strsep() returns NULL. + */ +char * +strsep(char **stringp, const char *delim) +{ + char *s; + const char *spanp; + int c, sc; + char *tok; + + if ((s = *stringp) == NULL) + return (NULL); + + for (tok = s; ; ) { + c = *s++; + spanp = delim; + do { + if ((sc = *spanp++) == c) { + if (c == 0) + s = NULL; + else + s[-1] = 0; + *stringp = s; + return (tok); + } + } while (sc != 0); + } + /* NOTREACHED */ +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_wins.c b/usr/src/lib/smbsrv/libsmb/common/smb_wins.c new file mode 100644 index 0000000000..09498abfca --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_wins.c @@ -0,0 +1,210 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * SMB WINS support functions + */ + +#include <strings.h> +#include <stdlib.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <smbsrv/smbinfo.h> + +#include <smbsrv/libsmb.h> + +/* + * smb_wins_iplist + * + * Get a string containing a list of comma separated IP addresses + * and return an array containing numeric equivalent for string IPs. + * + * Returns the number of parsed IPs. + * Return -1 if list is badly formatted. + * This routine need fix for IPv6 + */ +int +smb_wins_iplist(char *list, uint32_t iplist[], int max_naddr) +{ + char *ip, *ctx; + char *tmp; + int n = 0; + + if ((list == NULL) || (*list == '\0')) + return (0); + + if ((tmp = strdup(list)) == NULL) + return (0); + + ip = strtok_r(tmp, ",", &ctx); + while (ip && (n < max_naddr)) { + ip = trim_whitespace(ip); + if (*ip != 0) { + if (inet_pton(AF_INET, ip, &iplist[n]) == 1) { + n++; + } else { + return (-1); + } + } + ip = strtok_r(0, ",", &ctx); + } + + free(tmp); + return (n); +} + +/* + * smb_wins_is_excluded + * + * Check to see if the given IP addr shouldn't be registered in WINS. + * + * Returns 1 if it's excluded, 0 if it's not. + */ +boolean_t +smb_wins_is_excluded(in_addr_t ipaddr, + unsigned long *exclude_list, int nexclude) +{ + int i; + + if (nexclude == 0) + return (B_FALSE); + + for (i = 0; i < nexclude; i++) + if (ipaddr == exclude_list[i]) { + return (B_TRUE); + } + + return (B_FALSE); +} + +/* + * Build a CSV list of ips to be excluded. + * This function needs fix for IPv6 + */ +void +smb_wins_build_list(char *buf, uint32_t iplist[], int max_naddr) +{ + char ipstr[16]; + int i; + + if (!buf) + return; + + buf[0] = '\0'; + for (i = 0; i < max_naddr; i++) { + /* XXX these will be removed */ + /*LINTED*/ + if (iplist[i] == -1) + continue; + + if (inet_ntop(AF_INET, (const void *)(&iplist[i]), ipstr, + sizeof (ipstr)) == 0) + continue; + (void) strcat(buf, ipstr); + (void) strcat(buf, ","); + } + buf[strlen(buf)-1] = '\0'; +} + +/* + * This function build the new WINS exclude list from + * configured list + new additions to exclude list + * It also assumes that the buffers are of enough space. + */ +int +smb_wins_exclude_list(char *config_list, char *exclude_list) +{ + int ccnt, ecnt, already_there; + int i, j; + uint32_t ncur_list[SMB_PI_MAX_NETWORKS]; + uint32_t ecur_list[SMB_PI_MAX_NETWORKS]; + + ccnt = smb_wins_iplist(config_list, ncur_list, SMB_PI_MAX_NETWORKS); + if (ccnt < 0) + return (-1); + + ecnt = smb_wins_iplist(exclude_list, ecur_list, SMB_PI_MAX_NETWORKS); + if (ecnt < 0) + return (-1); + + if ((ccnt + ecnt) > SMB_PI_MAX_NETWORKS) + return (-1); + + for (i = 0; i < ecnt; i++) { + already_there = 0; + for (j = 0; j < ccnt; j++) { + if (ncur_list[j] == ecur_list[i]) { + already_there = 1; + } + } + if (already_there) + continue; + + ncur_list[ccnt++] = ecur_list[i]; + } + + smb_wins_build_list(config_list, ncur_list, ccnt); + return (0); +} + +/* + * This function build the new WINS allow list from + * configured list - new allowed list + * It also assumes that the buffers are of enough space. + */ +int +smb_wins_allow_list(char *config_list, char *allow_list) +{ + int ccnt, acnt; + int i, j; + uint32_t ncur_list[SMB_PI_MAX_NETWORKS]; + uint32_t acur_list[SMB_PI_MAX_NETWORKS]; + + ccnt = smb_wins_iplist(config_list, ncur_list, SMB_PI_MAX_NETWORKS); + if (ccnt < 0) + return (-1); + + acnt = smb_wins_iplist(allow_list, acur_list, SMB_PI_MAX_NETWORKS); + if (acnt < 0) + return (0); + + for (i = 0; i < acnt; i++) { + for (j = 0; j < ccnt; j++) { + if (ncur_list[j] == (in_addr_t)(-1)) + continue; + if (ncur_list[j] == acur_list[i]) { + ncur_list[j] = (in_addr_t)(-1); + } + } + } + smb_wins_build_list(config_list, ncur_list, ccnt); + return (0); +} diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_wksids.c b/usr/src/lib/smbsrv/libsmb/common/smb_wksids.c new file mode 100644 index 0000000000..0a40b95418 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_wksids.c @@ -0,0 +1,350 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module provides the interface to builtin domain information. + * These are the predefined groups and aliases in the NT AUTHORITY or + * BUILTIN domains, and some other miscellaneous bits. + */ + +#include <string.h> +#include <synch.h> +#include <smbsrv/ntsid.h> +#include <smbsrv/string.h> +#include <smbsrv/alloc.h> + +/* + * This table should contain all of the NT builtin domain names. + */ +static char *domain[] = { + "LOCAL", + "BUILTIN", + "NT AUTHORITY", + "UNKNOWN" +}; + +static int wk_init = 0; +static rwlock_t wk_rwlock; + +/* + * This table should contain all of the builtin domains, groups and + * aliases. The order is important because we do string compares on + * the SIDs. For each domain, ensure that the domain SID appears + * before any aliases in that domain. + */ +static well_known_account_t wkt[] = { + { SidTypeWellKnownGroup, 0, "S-1-0-0", "Null", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 1, "S-1-1-0", "Everyone", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 1, "S-1-2-0", "LOCAL", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 1, "S-1-3-0", "CREATOR OWNER", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 1, "S-1-3-1", "CREATOR GROUP", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 1, "S-1-3-2", "CREATOR OWNER SERVER", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 1, "S-1-3-3", "CREATOR GROUP SERVER", + LGF_HIDDEN, 0, NULL}, + { SidTypeDomain, 1, "S-1-4", "NON UNIQUE", + LGF_HIDDEN, 0, NULL}, + { SidTypeDomain, 2, "S-1-5", "NT AUTHORITY", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-1", "DIALUP", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-2", "NETWORK", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-3", "BATCH", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-4", "INTERACTIVE", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-6", "SERVICE", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-7", "ANONYMOUS", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-8", "PROXY", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-9", "SERVER", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-10", "SELF", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-11", "Authenticated Users", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-12", "RESTRICTED", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-18", "SYSTEM", + LGF_HIDDEN, 0, NULL}, + { SidTypeWellKnownGroup, 2, "S-1-5-21", "NON_UNIQUE", + LGF_HIDDEN, 0, NULL}, + { SidTypeDomain, 2, "S-1-5-32", "BUILTIN", + LGF_HIDDEN, 0, NULL}, + { SidTypeAlias, 1, "S-1-5-32-544", "Administrators", + 0, "Members can fully administer the computer/domain", NULL }, + { SidTypeAlias, 1, "S-1-5-32-545", "Users", + LGF_HIDDEN, 0, NULL}, + { SidTypeAlias, 1, "S-1-5-32-546", "Guests", + LGF_HIDDEN, 0, NULL}, + { SidTypeAlias, 1, "S-1-5-32-547", "Power Users", + 0, "Members can share directories", NULL }, + { SidTypeAlias, 1, "S-1-5-32-548", "Account Operators", + LGF_HIDDEN, 0, NULL}, + { SidTypeAlias, 1, "S-1-5-32-549", "Server Operators", + LGF_HIDDEN, 0, NULL}, + { SidTypeAlias, 1, "S-1-5-32-550", "Print Operators", + LGF_HIDDEN, 0, NULL}, + { SidTypeAlias, 1, "S-1-5-32-551", "Backup Operators", + 0, "Members can bypass file security to back up files", NULL }, + { SidTypeAlias, 1, "S-1-5-32-552", "Replicator", + LGF_HIDDEN, 0, NULL} +}; + + +/* + * nt_builtin_lookup_sid + * + * Search the wkt looking for a match on the specified SID. If the + * SID matches a builtin entry, the associated name is returned. + * Otherwise a null pointer is returned. + */ +char * +nt_builtin_lookup_sid(nt_sid_t *sid, WORD *sid_name_use) +{ + well_known_account_t *entry; + char *sidbuf; + int sidlen; + int i; + + if ((sidbuf = nt_sid_format(sid)) == 0) { + return (0); + } + + sidlen = strlen(sidbuf); + + for (i = 0; i < sizeof (wkt)/sizeof (wkt[0]); ++i) { + entry = &wkt[i]; + + if (strncmp(sidbuf, entry->sid, sidlen) == 0) { + if (sid_name_use) + *sid_name_use = entry->sid_name_use; + free(sidbuf); + return (entry->name); + } + } + + free(sidbuf); + return (0); +} + + +/* + * nt_builtin_lookup_name + * + * Search the wkt looking for a match on the specified name. If the + * name matches a builtin entry, the associated SID (which is in + * malloc'd memory) is returned. Otherwise a null pointer is returned. + */ +nt_sid_t * +nt_builtin_lookup_name(char *name, WORD *sid_name_use) +{ + well_known_account_t *entry; + int i; + + for (i = 0; i < sizeof (wkt)/sizeof (wkt[0]); ++i) { + entry = &wkt[i]; + + if (!utf8_strcasecmp(name, entry->name)) { + if (sid_name_use) + *sid_name_use = entry->sid_name_use; + return (nt_sid_strtosid(entry->sid)); + } + } + + return (0); +} + +/* + * nt_builtin_lookup + * + * Search the wkt looking for a match on the specified name. If the + * name matches a builtin entry then pointer to that entry will be + * returned. Otherwise 0 is returned. + */ +well_known_account_t * +nt_builtin_lookup(char *name) +{ + well_known_account_t *entry; + int i; + + (void) rw_rdlock(&wk_rwlock); + for (i = 0; i < sizeof (wkt)/sizeof (wkt[0]); ++i) { + entry = &wkt[i]; + + if (!utf8_strcasecmp(name, entry->name)) { + (void) rw_unlock(&wk_rwlock); + return (entry); + } + } + + (void) rw_unlock(&wk_rwlock); + return (0); +} + + +/* + * nt_builtin_is_wellknown + * + * Search the wkt looking for a match on the specified name. If the + * name matches a builtin entry returns 1. Otherwise returns 0. + */ +int +nt_builtin_is_wellknown(char *name) +{ + well_known_account_t *entry; + int i; + + for (i = 0; i < sizeof (wkt)/sizeof (wkt[0]); ++i) { + entry = &wkt[i]; + + if (!utf8_strcasecmp(name, entry->name)) { + return (1); + } + } + + return (0); +} + +/* + * nt_builtin_lookup_domain + * + * Return the builtin domain name for the specified alias or group name. + */ +char * +nt_builtin_lookup_domain(char *name) +{ + well_known_account_t *entry; + char *domain_name; + int i; + + for (i = 0; i < sizeof (wkt)/sizeof (wkt[0]); ++i) { + entry = &wkt[i]; + + if (!utf8_strcasecmp(name, entry->name)) { + domain_name = domain[entry->domain_ix]; + return (domain_name); + } + } + + return (0); +} + +/* + * nt_builtin_findfirst + * + * Returns pointer to the first entry of well known sids table. + */ +well_known_account_t * +nt_builtin_findfirst(DWORD *iterator) +{ + *iterator = 1; + return (&wkt[0]); +} + +/* + * nt_builtin_findnext + * + * Returns pointer to the entry of well known sids table specified + * by the iterator. Increments iterator to point to the next entry. + */ +well_known_account_t * +nt_builtin_findnext(DWORD *iterator) +{ + if (*iterator < sizeof (wkt)/sizeof (wkt[0])) + return (&wkt[(*iterator)++]); + + return (0); +} + +/* + * nt_builtin_init + * + * Generate binary SIDs from the string SIDs in the table + * and set the proper field. + * + * Caller MUST not store the binary SID pointer anywhere that + * could lead to freeing it. + * + * This function should only be called once. + */ +int +nt_builtin_init() +{ + well_known_account_t *entry; + int i; + + (void) rw_wrlock(&wk_rwlock); + if (wk_init) { + (void) rw_unlock(&wk_rwlock); + return (1); + } + + for (i = 0; i < sizeof (wkt)/sizeof (wkt[0]); ++i) { + entry = &wkt[i]; + entry->binsid = nt_sid_strtosid(entry->sid); + if (entry->binsid == NULL) { + (void) rw_unlock(&wk_rwlock); + nt_builtin_fini(); + return (0); + } + } + + wk_init = 1; + (void) rw_unlock(&wk_rwlock); + return (1); +} + +void +nt_builtin_fini() +{ + int i; + + (void) rw_wrlock(&wk_rwlock); + if (wk_init == 0) { + (void) rw_unlock(&wk_rwlock); + return; + } + + for (i = 0; i < sizeof (wkt)/sizeof (wkt[0]); ++i) { + if (wkt[i].binsid) { + free(wkt[i].binsid); + wkt[i].binsid = NULL; + } + } + + wk_init = 0; + (void) rw_unlock(&wk_rwlock); +} diff --git a/usr/src/lib/smbsrv/libsmb/i386/Makefile b/usr/src/lib/smbsrv/libsmb/i386/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/i386/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libsmb/sparc/Makefile b/usr/src/lib/smbsrv/libsmb/sparc/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/sparc/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libsmb/sparcv9/Makefile b/usr/src/lib/smbsrv/libsmb/sparcv9/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/sparcv9/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/smbsrv/libsmbns/Makefile b/usr/src/lib/smbsrv/libsmbns/Makefile new file mode 100644 index 0000000000..495ff5688d --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +HDRS= libsmbns.h + +include ../Makefile.smbsrv diff --git a/usr/src/lib/smbsrv/libsmbns/Makefile.com b/usr/src/lib/smbsrv/libsmbns/Makefile.com new file mode 100644 index 0000000000..2166b2640d --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/Makefile.com @@ -0,0 +1,62 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +LIBRARY= libsmbns.a +VERS= .1 + +OBJS_SHARED = \ + smb_netbios_util.o \ + +OBJS_COMMON= \ + smbns_ads.o \ + smbns_browser.o \ + smbns_dyndns.o \ + smbns_krb.o \ + smbns_ksetpwd.o \ + smbns_netbios.o \ + smbns_netbios_cache.o \ + smbns_netbios_datagram.o\ + smbns_netbios_name.o \ + smbns_netlogon.o \ + smbns_nicconfig.o + +OBJECTS= $(OBJS_COMMON) $(OBJS_SHARED) + +include ../../../Makefile.lib +include ../../Makefile.lib + +SRCS= $(OBJS_COMMON:%.o=$(SRCDIR)/%.c) \ + $(OBJS_SHARED:%.o=$(SRC)/common/smbsrv/%.c) + +LDLIBS += -lsmb -lgss -lldap -lresolv -lnsl -lsocket -lc +CPPFLAGS += -D_REENTRANT + +# DYNLIB libraries do not have lint libs and are not linted +$(DYNLIB) := LDLIBS += -lkrb5 + +include ../../Makefile.targ +include ../../../Makefile.targ diff --git a/usr/src/lib/smbsrv/libsmbns/amd64/Makefile b/usr/src/lib/smbsrv/libsmbns/amd64/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/amd64/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/smbsrv/libsmbns/common/libsmbns.h b/usr/src/lib/smbsrv/libsmbns/common/libsmbns.h new file mode 100644 index 0000000000..a05c197c35 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/libsmbns.h @@ -0,0 +1,184 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBSMBNS_H +#define _LIBSMBNS_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <ldap.h> +#include <net/if.h> + +#include <smbsrv/libsmb.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* ADS typedef/data structures and functions */ +#define ADS_MAXBUFLEN 100 + +typedef struct ads_handle_s { + char *user; /* admin user to create share in ADS */ + char *pwd; /* user password */ + char *container; /* user container in ADS */ + char *domain; /* ADS domain */ + char *domain_dn; /* domain in Distinquish Name format */ + char *ip_addr; /* ip addr in string format */ + char *hostname; /* fully qualified hostname */ + char *site; /* local ADS site */ + LDAP *ld; /* LDAP handle */ +} ADS_HANDLE; + +/* + * The possible return status of the adjoin routine. + */ +typedef enum adjoin_status { + ADJOIN_SUCCESS = 0, + ADJOIN_ERR_GET_HANDLE, + ADJOIN_ERR_GEN_PASSWD, + ADJOIN_ERR_ADD_TRUST_ACCT, + ADJOIN_ERR_GET_ENCTYPES, + ADJOIN_ERR_GET_HOST_PRINC, + ADJOIN_ERR_INIT_KRB_CTX, + ADJOIN_ERR_GET_KRB_PRINC, + ADJOIN_ERR_KSETPWD, + ADJOIN_ERR_MOD_TRUST_ACCT, + ADJOIN_ERR_WRITE_KEYTAB, + ADJOIN_ERR_IDMAP_SET_DOMAIN, + ADJOIN_ERR_IDMAP_SET_GC, + ADJOIN_ERR_IDMAP_REFRESH, + ADJOIN_ERR_IDMAP_CCACHE, + + ADJOIN_NUM_STATUS +} adjoin_status_t; + +/* ADS functions */ +extern ADS_HANDLE *ads_open(void); +extern void ads_close(ADS_HANDLE *); +extern int ads_publish_share(ADS_HANDLE *, const char *, const char *, + const char *, const char *); +extern int ads_remove_share(ADS_HANDLE *, const char *, const char *, + const char *, const char *); +extern int ads_build_unc_name(char *, int, const char *, const char *); +extern int ads_lookup_share(ADS_HANDLE *, const char *, const char *, char *); +extern int ads_add_share(ADS_HANDLE *, const char *, const char *, + const char *); + +extern adjoin_status_t adjoin(char *, int); +extern char *adjoin_report_err(adjoin_status_t status); + +/* DYNDNS functions */ +extern int dyndns_update(void); +extern int dyndns_clear_rev_zone(void); + +/* Kerberos initialization function */ +extern int smb_kinit(char *user, char *passwd); + + +/* NETBIOS Functions */ +extern int msdcs_lookup_ads(void); +extern void smb_netbios_start(void); +extern void smb_netbios_shutdown(void); +extern void smb_netbios_name_reconfig(void); + +/* Browser Configure */ +extern void smb_browser_config(void); + +extern void smb_netlogon_request(int, int, char *); + +/* + * NIC listing and config + */ +#define MAXIFS 256 +#define SIZE_IP 17 + +typedef struct { + char ifname[LIFNAMSIZ]; + uint32_t ip; + uint32_t mask; + uint32_t broadcast; + boolean_t exclude; + uint64_t flags; + char groupname[LIFGRNAMSIZ]; + char **aliases; + int naliases; +} net_cfg_t; +typedef struct { + net_cfg_t *net_cfg_list; + int net_cfg_cnt; +} net_cfg_list_t; + +struct if_list { + char name[IFNAMSIZ+1]; + struct if_list *next; +}; + +struct ip_alias { + char name[SIZE_IP]; + struct ip_alias *next; +}; + +#define GATEWAY_FILE "/etc/defaultrouter" + +/* NIC Config functions */ +extern void smb_resolver_init(void); +extern void smb_resolver_close(void); +extern int smb_get_nameservers(struct in_addr *, int); +extern uint16_t smb_get_next_resid(void); +extern void smb_nic_lock(void); +extern void smb_nic_unlock(void); +extern int smb_nic_init(void); +extern void smb_nic_build_info(void); +extern net_cfg_t *smb_nic_get_byind(int, net_cfg_t *); +extern net_cfg_t *smb_nic_get_bysubnet(uint32_t, net_cfg_t *); +extern net_cfg_t *smb_nic_get_byip(uint32_t, net_cfg_t *); +extern int smb_nic_get_num(void); +extern int smb_nic_get_IP(char *, uint32_t *uip); +extern int smb_nic_get_broadcast(char *, uint32_t *uip); +extern int smb_nic_get_netmask(char *, uint32_t *uip); +extern int smb_nic_get_IP_aliases(char *, struct ip_alias **); +extern int smb_nic_get_number(void); +extern int smb_nic_get_num_physical(void); +extern int smb_nic_get_num_logical(void); +extern int smb_nic_get_num_aliases(char *); +extern int smb_nic_get_default_gateway(char *, unsigned int); +extern int smb_nic_flags(char *, uint64_t *); +extern int smb_nic_build_if_name(char ***); +extern int smb_nic_build_network_structures(net_cfg_t **, int *); +extern char *smb_nic_get_ifnames(int, int); +extern int smb_nic_validate_ip_address(char *); +extern int smb_nic_status(char *, uint64_t); +extern int smb_nic_get_group(char *lifname, char *grname); +extern int smb_nic_set_group(char *lifname, char *grname); +extern int smb_nic_clear_niclist(net_cfg_t *, int); +extern int smb_nic_clear_name_list(char **, int); +extern int smb_nic_clear_ip_alias(struct ip_alias *); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSMBNS_H */ diff --git a/usr/src/lib/smbsrv/libsmbns/common/llib-lsmbns b/usr/src/lib/smbsrv/libsmbns/common/llib-lsmbns new file mode 100644 index 0000000000..5420d543b5 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/llib-lsmbns @@ -0,0 +1,31 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include <smbsrv/libsmbns.h> diff --git a/usr/src/lib/smbsrv/libsmbns/common/mapfile-vers b/usr/src/lib/smbsrv/libsmbns/common/mapfile-vers new file mode 100644 index 0000000000..d20641252d --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/mapfile-vers @@ -0,0 +1,81 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +SUNWprivate { + global: + adjoin; + adjoin_report_err; + ads_add_share; + ads_build_unc_name; + ads_close; + ads_lookup_share; + ads_open; + ads_publish_share; + ads_remove_share; + dyndns_clear_rev_zone; + dyndns_update; + msdcs_lookup_ads; + smb_browser_config; + smb_get_nameservers; + smb_get_next_resid; + smb_kinit; + smb_netbios_name_reconfig; + smb_netbios_start; + smb_netbios_shutdown; + smb_netlogon_request; + smb_nic_build_if_name; + smb_nic_build_network_structures; + smb_nic_clear_ip_alias; + smb_nic_clear_name_list; + smb_nic_clear_niclist; + smb_nic_free_niclist; + smb_nic_get_IP; + smb_nic_get_IP_aliases; + smb_nic_get_broadcast; + smb_nic_build_info; + smb_nic_get_byind; + smb_nic_get_byip; + smb_nic_get_bysubnet; + smb_nic_get_default_gateway; + smb_nic_get_group; + smb_nic_get_ifnames; + smb_nic_get_netmask; + smb_nic_get_num; + smb_nic_get_num_aliases; + smb_nic_get_number; + smb_nic_get_num_logical; + smb_nic_get_num_physical; + smb_nic_init; + smb_nic_lock; + smb_nic_status; + smb_nic_unlock; + smb_nic_validate_ip_address; + smb_resolver_close; + smb_resolver_init; + local: + *; +}; diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c new file mode 100644 index 0000000000..c57971e455 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.c @@ -0,0 +1,2304 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/param.h> +#include <ldap.h> +#include <stdlib.h> +#include <gssapi/gssapi.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/time.h> +#include <netdb.h> +#include <pthread.h> +#include <unistd.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include <sys/synch.h> +#include <string.h> +#include <strings.h> +#include <syslog.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <smbsrv/libsmbns.h> +#include <smbns_ads.h> +#include <smbns_dyndns.h> +#include <smbns_krb.h> + +#define ADS_DN_MAX 300 +#define ADS_MAXMSGLEN 512 +#define ADS_HOST_PREFIX "host/" +#define ADS_COMPUTERS_CN "Computers" + +/* current ADS server to communicate with */ +ADS_HOST_INFO *ads_host_info = NULL; +mutex_t ads_mtx; + +/* + * adjoin_errmsg + * + * Use the adjoin return status defined in adjoin_status_t as the index + * to this table. + */ +static char *adjoin_errmsg[] = { + "ADJOIN succeeded.", + "ADJOIN failed to get handle.", + "ADJOIN failed to generate machine password.", + "ADJOIN failed to add workstation trust account.", + "ADJOIN failed to get list of encryption types.", + "ADJOIN failed to get host principal.", + "ADJOIN failed to initialize kerberos context.", + "ADJOIN failed to get kerberos principal.", + "ADJOIN failed to set machine account password on AD.", + "ADJOIN failed to modify workstation trust account.", + "ADJOIN failed to write Keberos keytab file.", + "ADJOIN failed to configure idmap(mapping domain).", + "ADJOIN failed to configure idmap(global catalog).", + "ADJOIN failed to refresh idmap service." + "ADJOIN failed to remove idmap ccache." +}; + +static int ads_bind(ADS_HANDLE *); +static void ads_get_computer_dn(ADS_HANDLE *, char *, size_t); +static char *ads_get_host_principal(char *fqhost); +static char *ads_get_host_principal_w_realm(char *princ, char *domain); +static int ads_get_host_principals(char *fqhost, char *domain, + char **princ, char **princ_r); +static int ads_add_computer(ADS_HANDLE *ah); +static void ads_del_computer(ADS_HANDLE *ah); +static int ads_lookup_computer_n_attr(ADS_HANDLE *ah, char *attr, char **val); +static int ads_modify_computer(ADS_HANDLE *ah, int des_only); +static krb5_kvno ads_lookup_computer_attr_kvno(ADS_HANDLE *ah); +static int ads_gen_machine_passwd(char *machine_passwd, int bufsz); +static void ads_set_host_info(ADS_HOST_INFO *host); +static ADS_HOST_INFO *ads_get_host_info(void); + +/* + * ads_build_unc_name + * + * Construct the UNC name of the share object in the format of + * \\hostname.domain\shareUNC + * + * Returns 0 on success, -1 on error. + */ +int +ads_build_unc_name(char *unc_name, int maxlen, + const char *hostname, const char *shareUNC) +{ + char my_domain[ADS_MAXBUFLEN]; + + if (smb_getdomainname(my_domain, sizeof (my_domain)) != 0) + return (-1); + + (void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s", + hostname, my_domain, shareUNC); + return (0); +} + +/* + * ads_skip_domain_name + * Skip domain name format in DNS message. The format is a sequence of + * ascii labels with each label having a length byte at the beginning. + * The domain name is terminated with a NULL character. + * i.e. 3sun3com0 + * Parameters: + * bufptr: address of pointer of buffer that contains domain name + * Returns: + * bufptr: points to the data after the domain name label + */ +static void +ads_skip_domain_name(char **bufptr) +{ + int i = 0; + unsigned char c, d; + + c = (*bufptr)[i++]; + d = c & 0xC0; + while (c != 0 && (d != 0xC0)) { /* do nothing */ + c = (*bufptr)[i++]; + d = c & 0xC0; + } + + if (d == 0xC0) + /* skip 2nd byte in 2 byte ptr info */ + i++; + *bufptr += i; +} + +static int +ads_is_ptr(char *buf, int len, char *offset_ptr, char **new_loc) +{ + uint16_t offset; + unsigned char c; + + c = len & 0xC0; + if (c == 0xC0) { + offset_ptr = dyndns_get_nshort(offset_ptr, &offset); + offset &= 0x3FFF; + if (offset > NS_PACKETSZ) { + return (-1); + } + *new_loc = buf + offset; + return (1); + } + return (0); +} + +/* + * ads_get_domain_name + * Converts the domain name format in DNS message back to string format. + * The format is a sequence of ascii labels with each label having a length + * byte at the beginning. The domain name is terminated with a NULL + * character. + * i.e. 6procom3com0 -> procom.com + * Parameters: + * bufptr : address of pointer to buffer that contains domain name + * dname_len: length of domain name in label format + * Returns: + * NULL : error + * domain name: in string format using allocated memory + * bufptr : points to the data after the domain name label + */ +static char * +ads_get_domain_name(char *buf, char **bufptr) +{ + char str[256], *ptr, *new_loc; + int i, j, k, len, ret; + int skip = 0; + i = 0; + k = 0; + ptr = *bufptr; + + /* get len of first label */ + len = ptr[i++]; + if ((ret = ads_is_ptr(buf, len, &ptr[i-1], &new_loc)) == 1) { + if (skip == 0) { + /* skip up to first ptr */ + skip = i; + } + + i = 0; + ptr = new_loc; + + /* get len of first label */ + len = ptr[i++]; + } else { + if (ret == -1) { + return (NULL); + } + } + + while (len) { + if ((len > 63) || (k >= 255)) + return (NULL); + + for (j = 0; j < len; j++) + str[k++] = ptr[i++]; + + /* get len of next label */ + len = ptr[i++]; + if ((ret = ads_is_ptr(buf, len, &ptr[i-1], &new_loc)) == 1) { + if (skip == 0) { + /* skip up to first ptr */ + skip = i; + } + i = 0; + ptr = new_loc; + + /* get len of first label */ + len = ptr[i++]; + } else if (ret == -1) { + return (NULL); + } + + if (len) { + /* replace label len or ptr with '.' */ + str[k++] = '.'; + } + } + + str[k] = 0; + + if (skip) { + /* skip name with ptr or just ptr */ + *bufptr += skip + 1; + } else { + /* skip name */ + *bufptr += i; + } + + return (strdup(str)); +} + +/* + * ads_ping + * Ping IP without displaying log. This is used to ping an ADS server to see + * if it is still alive before connecting to it with TCP. + * Taken from os/service/ping.c + * Parameters: + * hostinetaddr: 4 bytes IP address in network byte order + * Returns: + * -1: error + * 0: successful + */ +/*ARGSUSED*/ +static int +ads_ping(unsigned long hostinetaddr) +{ + return (0); +} + +/* + * ads_free_host_list + */ +static void +ads_free_host_list(ADS_HOST_INFO *host_list, int count) +{ + int i; + for (i = 0; i < count; i++) { + free(host_list[i].name); + } + free(host_list); +} + +/* + * ads_set_host_info + * Cache the result of the ADS discovery if the cache is empty. + */ +static void +ads_set_host_info(ADS_HOST_INFO *host) +{ + (void) mutex_lock(&ads_mtx); + if (!ads_host_info) + ads_host_info = host; + (void) mutex_unlock(&ads_mtx); +} + +/* + * ads_get_host_info + * Get the cached ADS host info. + */ +static ADS_HOST_INFO * +ads_get_host_info(void) +{ + ADS_HOST_INFO *host; + + (void) mutex_lock(&ads_mtx); + host = ads_host_info; + (void) mutex_unlock(&ads_mtx); + return (host); +} +/* + * ads_find_host + * This routine builds a DNS service location message and sends it to the + * DNS server via UDP to query it for a list of ADS server(s). Once a reply + * is received, the reply message is parsed to get the hostname and IP + * addresses of the ADS server(s). One ADS server will be selected from the + * list. A ping is sent to each host at a time and the one that respond will + * be selected. + * + * The service location of _ldap._tcp.dc.msdcs.<ADS domain> is used to + * guarantee that Microsoft domain controllers are returned. Microsoft domain + * controllers are also ADS servers. + * + * The ADS hostnames are stored in the answer section of the DNS reply message. + * The IP addresses are stored in the additional section. If the additional + * section does not contain any IP addresses then a DNS query by hostname is + * sent to get the IP address of the hostname. This is very unlikely. + * + * The DNS reply message may be in compress formed. The compression is done + * on repeating domain name label in the message. i.e hostname. + * Parameters: + * ns: Nameserver to use to find the ADS host + * domain: domain of ADS host. + * Returns: + * ADS host: fully qualified hostname, ip address, ldap port + * port : LDAP port of ADS host + */ +/*ARGSUSED*/ +ADS_HOST_INFO * +ads_find_host(char *ns, char *domain, int *port, char *service, int *go_next) +{ + int s; + uint16_t id, rid, data_len, eport; + int ipaddr; + struct hostent *h; + char buf[NS_PACKETSZ], buf2[NS_PACKETSZ]; + char *bufptr, *str; + int i, ret; + int queryReq; + uint16_t query_cnt, ans_cnt, namser_cnt, addit_cnt; + int quest_type, quest_class; + int dns_ip, decode_ip; + struct in_addr addr; + uint16_t flags = 0; + int force_recurs = 0; + ADS_HOST_INFO *ads_hosts_list = NULL, *ads_host; + ADS_HOST_INFO *ads_hosts_list2 = NULL; + + *go_next = 0; + + /* + * If we have already found an ADS server, skip the ads_find_host + * process. Returns the ADS host from the cache. + */ + ads_host = ads_get_host_info(); + if (ads_host) + return (ads_host); + + if (ns == NULL || *ns == 0) { + return (NULL); + } + dns_ip = inet_addr(ns); + + if ((s = dyndns_open_init_socket(SOCK_DGRAM, dns_ip, 53)) < 0) + return (NULL); + +retry: + /* build query request */ + queryReq = REQ_QUERY; + query_cnt = 1; + ans_cnt = 0; + namser_cnt = 0; + addit_cnt = 0; + + (void) memset(buf, 0, NS_PACKETSZ); + bufptr = buf; + id = smb_get_next_resid(); + if (dyndns_build_header(&bufptr, BUFLEN_UDP(bufptr, buf), id, queryReq, + query_cnt, ans_cnt, namser_cnt, addit_cnt, flags) == -1) { + (void) close(s); + return (NULL); + } + + quest_type = ns_t_srv; + quest_class = ns_c_in; + + if (dyndns_build_quest_zone(&bufptr, BUFLEN_UDP(bufptr, buf), service, + quest_type, quest_class) == -1) { + (void) close(s); + return (NULL); + } + + if (dyndns_udp_send_recv(s, buf, bufptr - buf, buf2) == -1) { + (void) close(s); + syslog(LOG_ERR, "smb_ads: send/receive error"); + *go_next = 1; + return (NULL); + } + (void) close(s); + + (void) dyndns_get_nshort(buf2, &rid); + if (id != rid) + return (NULL); + + /* + * check if query is successful by checking error + * field in UDP + */ + ret = buf2[3] & 0xf; + if (ret != NOERROR) { + syslog(LOG_ERR, "smb_ads: DNS query for ADS host error: %d: ", + ret); + dyndns_msg_err(ret); + *go_next = 1; + return (NULL); + } + + bufptr = buf2; + bufptr += 2; /* Skip ID section */ + bufptr = dyndns_get_nshort(bufptr, &flags); + bufptr = dyndns_get_nshort(bufptr, &query_cnt); + bufptr = dyndns_get_nshort(bufptr, &ans_cnt); + bufptr = dyndns_get_nshort(bufptr, &namser_cnt); + bufptr = dyndns_get_nshort(bufptr, &addit_cnt); + + if (ans_cnt == 0) { + /* Check if the server supports recursive queries */ + if (force_recurs++ == 0 && (flags & DNSF_RECUR_SUPP) != 0) { + flags = DNSF_RECUR_QRY; + goto retry; + } + + syslog(LOG_DEBUG, "smb_ads: No ADS host found: " + "No answer section\n"); + return (NULL); + } + + /* skip question section */ + if (query_cnt == 1) { + ads_skip_domain_name(&bufptr); + bufptr += 4; + } else { + syslog(LOG_ERR, "smb_ads: No ADS host found, malformed " + "question section, query_cnt: %d???\n", query_cnt); + return (NULL); + } + + ads_hosts_list = (ADS_HOST_INFO *) + malloc(sizeof (ADS_HOST_INFO)*ans_cnt); + if (ads_hosts_list == NULL) + return (NULL); + + bzero(ads_hosts_list, sizeof (ADS_HOST_INFO) * ans_cnt); + + /* check answer section */ + for (i = 0; i < ans_cnt; i++) { + ads_skip_domain_name(&bufptr); + + /* skip type, class, ttl */ + bufptr += 8; + + /* len of data after this point */ + bufptr = dyndns_get_nshort(bufptr, &data_len); + + /* skip priority, weight */ + bufptr += 4; + bufptr = dyndns_get_nshort(bufptr, &eport); + ads_hosts_list[i].port = eport; + + if ((str = ads_get_domain_name(buf2, &bufptr)) == NULL) { + syslog(LOG_ERR, "smb_ads: No ADS host found, " + "error decoding DNS answer section\n"); + ads_free_host_list(ads_hosts_list, ans_cnt); + return (NULL); + } + ads_hosts_list[i].name = str; + } + + /* check authority section */ + for (i = 0; i < namser_cnt; i++) { + ads_skip_domain_name(&bufptr); + + /* skip type, class, ttl */ + bufptr += 8; + + /* get len of data */ + bufptr = dyndns_get_nshort(bufptr, &data_len); + + /* skip data */ + bufptr += data_len; + } + + /* check additional section to get IP address of ads host */ + decode_ip = 1; + smb_config_rdlock(); + if (smb_config_getyorn(SMB_CI_ADS_IPLOOKUP) == 1) + decode_ip = 0; + smb_config_unlock(); + + if (decode_ip && (addit_cnt > 0)) { + int j; + + ads_hosts_list2 = (ADS_HOST_INFO *) + malloc(sizeof (ADS_HOST_INFO) * addit_cnt); + if (ads_hosts_list2 == NULL) { + ads_free_host_list(ads_hosts_list, ans_cnt); + return (NULL); + } + + bzero(ads_hosts_list2, sizeof (ADS_HOST_INFO) * addit_cnt); + + for (i = 0; i < addit_cnt; i++) { + + if ((str = ads_get_domain_name(buf2, + &bufptr)) == NULL) { + syslog(LOG_ERR, "smb_ads: No ADS host found, " + "error decoding DNS additional section\n"); + ads_free_host_list(ads_hosts_list, ans_cnt); + ads_free_host_list(ads_hosts_list2, addit_cnt); + return (NULL); + } + + ads_hosts_list2[i].name = str; + bufptr += 10; + bufptr = dyndns_get_int(bufptr, &ipaddr); + ads_hosts_list2[i].ip_addr = ipaddr; + } + + /* pick a host that is up */ + for (i = 0; i < addit_cnt; i++) { + if (ads_ping(ads_hosts_list2[i].ip_addr) != 0) { + continue; + } + for (j = 0; j < ans_cnt; j++) + if (strcmp(ads_hosts_list2[i].name, + ads_hosts_list[j].name) == 0) + break; + if (j == ans_cnt) { + ads_free_host_list(ads_hosts_list, ans_cnt); + ads_free_host_list(ads_hosts_list2, addit_cnt); + return (NULL); + } + ads_host = (ADS_HOST_INFO *) + malloc(sizeof (ADS_HOST_INFO)); + if (ads_host == NULL) { + ads_free_host_list(ads_hosts_list, ans_cnt); + ads_free_host_list(ads_hosts_list2, addit_cnt); + return (NULL); + } + bzero(ads_host, sizeof (ADS_HOST_INFO)); + ads_host->name = strdup(ads_hosts_list[j].name); + if (ads_host->name == NULL) { + ads_free_host_list(ads_hosts_list, ans_cnt); + ads_free_host_list(ads_hosts_list2, addit_cnt); + return (NULL); + } + ads_host->ip_addr = ads_hosts_list2[i].ip_addr; + ads_host->port = ads_hosts_list[j].port; + *port = ads_host->port; + addr.s_addr = ads_host->ip_addr; + syslog(LOG_DEBUG, "smb_ads: Found ADS server: %s (%s)" + " from %s\n", ads_host->name, inet_ntoa(addr), ns); + ads_free_host_list(ads_hosts_list, ans_cnt); + ads_free_host_list(ads_hosts_list2, addit_cnt); + ads_set_host_info(ads_host); + return (ads_host); + } + ads_free_host_list(ads_hosts_list2, addit_cnt); + } else { + /* use DNS to get IP address of ads host */ + /* + * Shouldn't get here unless entries exist in + * DNS but DNS server did + * not put them in additional section of DNS reply packet. + */ + for (i = 0; i < ans_cnt; i++) { + h = gethostbyname(ads_hosts_list[i].name); + if (h == NULL) + continue; + if (h->h_addr == NULL) + continue; + (void) memcpy(&ads_hosts_list[i].ip_addr, + h->h_addr, sizeof (addr.s_addr)); + if (ads_ping(ads_hosts_list[i].ip_addr) == 0) { + ads_host = (ADS_HOST_INFO *) + malloc(sizeof (ADS_HOST_INFO)); + if (ads_host == NULL) { + ads_free_host_list(ads_hosts_list, + ans_cnt); + return (NULL); + } + bzero(ads_host, sizeof (ADS_HOST_INFO)); + ads_host->name = strdup(ads_hosts_list[i].name); + if (ads_host->name == NULL) { + ads_free_host_list(ads_hosts_list, + ans_cnt); + return (NULL); + } + ads_host->ip_addr = ads_hosts_list[i].ip_addr; + ads_host->port = ads_hosts_list[i].port; + *port = ads_host->port; + addr.s_addr = ads_host->ip_addr; + syslog(LOG_DEBUG, "smb_ads: Found ADS server" + " using DNS: %s (%s) port %d", + ads_host->name, inet_ntoa(addr), + ads_host->port); + ads_free_host_list(ads_hosts_list, ans_cnt); + ads_set_host_info(ads_host); + return (ads_host); + } + } + } + syslog(LOG_ERR, "smb_ads: Can't get IP for " + "ADS host or ADS host is down.\n"); + ads_free_host_list(ads_hosts_list, ans_cnt); + + *go_next = 1; + return (NULL); +} + +/* + * ads_convert_domain + * Converts a domain string into its distinguished name i.e. a unique + * name for an entry in the Directory Service. + * Memory is allocated + * for the new string. + * i.e. procom.com -> dc=procom,dc=com + * Parameters: + * s: fully qualified DNS domain string + * Returns: + * NULL if error + * DNS domain in LDAP DN string format + */ +static char * +ads_convert_domain(char *s) +{ + char *t, *s2, *t2; + int len, cnt; + + if (s == NULL || *s == 0) + return (NULL); + + cnt = 0; + t = s; + while (*t) { + if (*t++ == '.') { + cnt++; + } + } + + len = 3 + strlen(s) + cnt*3 + 1; + + s2 = (char *)malloc(len); + if (s2 == NULL) + return (NULL); + + bzero(s2, len); + + t = s2; + (void) strncpy(t, "dc=", 3); + t += 3; + t2 = s; + while (*s) { + if (*s == '.') { + if (t + 3 >= s2 + len - 1) { + syslog(LOG_ERR, "[ads_convert_domain] " + "buffer overrun for string " + "conversion of %s: tot buf " + "sz alloc: %d, last " + "written buf offset: %d\n", + t2, len, t+3-s2); + free(s2); + return (NULL); + } + (void) strncpy(t, ",dc=", 4); + t += 4; + s++; + } else { + if (t >= s2 + len - 1) { + syslog(LOG_ERR, "[ads_convert_domain] " + "buffer overrun for string " + "conversion of %s: tot buf " + "sz alloc: %d, last " + "written buf offset: %d\n", + t2, len, t-s2); + free(s2); + return (NULL); + } + *t++ = *s++; + } + } + *t = '\0'; + return (s2); +} + +/* + * ads_free_host_info + * Free the memory use by the global ads_host_info and set it to NULL. + */ +void +ads_free_host_info(void) +{ + (void) mutex_lock(&ads_mtx); + if (ads_host_info) { + free(ads_host_info->name); + free(ads_host_info); + ads_host_info = NULL; + } + (void) mutex_unlock(&ads_mtx); +} + +/* + * ads_open + * Open a LDAP connection to an ADS server. + * If ADS is enabled and the administrative username, password, container, and + * ADS domain are defined then query DNS to find an ADS server if this is the + * very first call to this routine. After an ADS server is found then this + * server will be used everytime this routine is called until the system is + * rebooted or the ADS server becomes unavailable then an ADS server will + * be queried again. The ADS server is always ping before an LDAP connection + * is made to it. If the pings fail then DNS is used once more to find an + * available ADS server. If the ping is successful then an LDAP connection + * is made to the ADS server. After the connection is made then an ADS handle + * is created to be returned. + * + * After the LDAP connection, the LDAP version will be set to 3 using + * ldap_set_option(). + * + * The ads_bind() routine is also called before the ADS handle is returned. + * Parameters: + * None + * Returns: + * NULL : can't connect to ADS server or other errors + * ADS_HANDLE* : handle to ADS server + */ +ADS_HANDLE * +ads_open(void) +{ + ADS_HANDLE *ah; + LDAP *ld; + int version = 3, ads_port, find_ads_retry; + char *adminUser, *password, *container; + char domain[MAXHOSTNAMELEN]; + int enable; + ADS_HOST_INFO *ads_host = NULL; + struct in_addr addr; + char *site, *service = NULL, *site_service = NULL; + int service_sz; + struct in_addr ns_list[MAXNS]; + int i, cnt, go_next; + + if (smb_getdomainname(domain, MAXHOSTNAMELEN) != 0) + return (NULL); + + smb_config_rdlock(); + enable = smb_config_getyorn(SMB_CI_ADS_ENABLE); + if (!enable) { + smb_config_unlock(); + return (NULL); + } + adminUser = smb_config_getstr(SMB_CI_ADS_USER); + if (adminUser == NULL || *adminUser == 0) { + syslog(LOG_ERR, "smb_ads: admin user is not set"); + smb_config_unlock(); + return (NULL); + } + password = smb_config_getstr(SMB_CI_ADS_PASSWD); + if (password == NULL || *password == 0) { + syslog(LOG_ERR, "smb_ads: admin user password is not set"); + smb_config_unlock(); + return (NULL); + } + container = smb_config_getstr(SMB_CI_ADS_USER_CONTAINER); + if (container == NULL || *container == 0) + container = "cn=Users"; + + site = smb_config_getstr(SMB_CI_ADS_SITE); + smb_config_unlock(); + + + + find_ads_retry = 0; +find_ads_host: + + ads_host = ads_get_host_info(); + if (!ads_host) { + if (site && *site != 0) { + service_sz = strlen("_ldap._tcp.._sites.dc._msdcs.") + + strlen(site) + strlen(domain) + 1; + site_service = (char *)malloc(service_sz); + if (site_service == NULL) { + syslog(LOG_ERR, "smb_ads: No ADS host found" + " malloc failed..."); + return (NULL); + } + (void) snprintf(site_service, service_sz, + "_ldap._tcp.%s._sites.dc._msdcs.%s", site, domain); + } + service_sz = strlen("_ldap._tcp.dc._msdcs.") + strlen(domain) + + 1; + service = (char *)malloc(service_sz); + if (service == NULL) { + syslog(LOG_ERR, "smb_ads: No ADS host found malloc" + " failed..."); + if (site_service != NULL) + (void) free(site_service); + return (NULL); + } + (void) snprintf(service, service_sz, "_ldap._tcp.dc._msdcs.%s", + domain); + + cnt = smb_get_nameservers(ns_list, MAXNS); + + ads_host = NULL; + go_next = 0; + for (i = 0; i < cnt; i++) { + if (site_service != NULL) { + ads_host = ads_find_host(inet_ntoa(ns_list[i]), + domain, &ads_port, site_service, &go_next); + } + if (ads_host == NULL) { + ads_host = ads_find_host(inet_ntoa(ns_list[i]), + domain, &ads_port, service, &go_next); + } + if (ads_host != NULL) + break; + if (go_next == 0) + break; + } + } + + if (site_service) + (void) free(site_service); + if (service) + (void) free(service); + + if (ads_host == NULL) { + syslog(LOG_ERR, "smb_ads: No ADS host found from " + "configured nameservers"); + return (NULL); + } + + if (ads_ping(ads_host->ip_addr) != 0) { + ads_free_host_info(); + ads_host = NULL; + if (find_ads_retry == 0) { + find_ads_retry = 1; + goto find_ads_host; + } + return (NULL); + } + + ah = (ADS_HANDLE *)malloc(sizeof (ADS_HANDLE)); + if (ah == NULL) { + return (NULL); + } + (void) memset(ah, 0, sizeof (ADS_HANDLE)); + + addr.s_addr = ads_host->ip_addr; + if ((ld = ldap_init((char *)inet_ntoa(addr), ads_host->port)) == NULL) { + syslog(LOG_ERR, "smb_ads: Could not open connection " + "to host: %s\n", ads_host->name); + ads_free_host_info(); + ads_host = NULL; + free(ah); + if (find_ads_retry == 0) { + find_ads_retry = 1; + goto find_ads_host; + } + return (NULL); + } + + if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version) + != LDAP_SUCCESS) { + syslog(LOG_ERR, "smb_ads: Could not set " + "LDAP_OPT_PROTOCOL_VERSION %d\n", version); + ads_free_host_info(); + free(ah); + (void) ldap_unbind(ld); + return (NULL); + } + + ah->ld = ld; + ah->user = strdup(adminUser); + ah->pwd = strdup(password); + ah->container = strdup(container); + ah->domain = strdup(domain); + + if ((ah->user == NULL) || (ah->pwd == NULL) || + (ah->container == NULL) || (ah->domain == NULL)) { + ads_close(ah); + return (NULL); + } + + ah->domain_dn = ads_convert_domain(domain); + if (ah->domain_dn == NULL) { + ads_close(ah); + return (NULL); + } + + ah->hostname = strdup(ads_host->name); + if (ah->hostname == NULL) { + ads_close(ah); + return (NULL); + } + if (site) { + ah->site = strdup(site); + if (ah->site == NULL) { + ads_close(ah); + return (NULL); + } + } else { + ah->site = NULL; + } + + if (ads_bind(ah) == -1) { + ads_close(ah); + return (NULL); + } + + return (ah); +} + +/* + * ads_close + * Close connection to ADS server and free memory allocated for ADS handle. + * LDAP unbind is called here. + * Parameters: + * ah: handle to ADS server + * Returns: + * void + */ +void +ads_close(ADS_HANDLE *ah) +{ + int len; + + if (ah == NULL) + return; + /* close and free connection resources */ + if (ah->ld) + (void) ldap_unbind(ah->ld); + + free(ah->user); + if (ah->pwd) { + len = strlen(ah->pwd); + /* zero out the memory that contains user's password */ + if (len > 0) + bzero(ah->pwd, len); + free(ah->pwd); + } + free(ah->container); + free(ah->domain); + free(ah->domain_dn); + free(ah->hostname); + free(ah->site); + free(ah); +} + +/* + * ads_display_stat + * Display error message for GSS-API routines. + * Parameters: + * maj: GSS major status + * min: GSS minor status + * Returns: + * None + */ +static void +ads_display_stat(OM_uint32 maj, OM_uint32 min) +{ + gss_buffer_desc msg; + OM_uint32 msg_ctx = 0; + OM_uint32 min2; + (void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, + &msg_ctx, &msg); + syslog(LOG_ERR, "smb_ads: major status error: %s\n", (char *)msg.value); + (void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, + &msg_ctx, &msg); + syslog(LOG_ERR, "smb_ads: minor status error: %s\n", (char *)msg.value); +} + +/* + * free_attr + * Free memory allocated when publishing a share. + * Parameters: + * addattrs: an array of LDAPMod pointers + * Returns: + * None + */ +static void +free_attr(LDAPMod *addattrs[]) +{ + int i; + for (i = 0; addattrs[i]; i++) { + free(addattrs[i]); + } +} + +/* + * ads_acquire_cred + * Called by ads_bind() to get a handle to administrative user's credential + * stored locally on the system. The credential is the TGT. If the attempt at + * getting handle fails then a second attempt will be made after getting a + * new TGT. + * Please look at ads_bind() for more information. + * + * Paramters: + * ah : handle to ADS server + * kinit_retry: if 0 then a second attempt will be made to get handle to the + * credential if the first attempt fails + * Returns: + * cred_handle: handle to the administrative user's credential (TGT) + * oid : contains Kerberos 5 object identifier + * kinit_retry: A 1 indicates that a second attempt has been made to get + * handle to the credential and no further attempts can be made + * -1 : error + * 0 : success + */ +static int +ads_acquire_cred(ADS_HANDLE *ah, gss_cred_id_t *cred_handle, gss_OID *oid, + int *kinit_retry) +{ + return (krb5_acquire_cred_kinit(ah->user, ah->pwd, cred_handle, oid, + kinit_retry, "ads")); +} + +/* + * ads_establish_sec_context + * Called by ads_bind() to establish a security context to an LDAP service on + * an ADS server. If the attempt at establishing the security context fails + * then a second attempt will be made by ads_bind() if a new TGT has not been + * already obtained in ads_acquire_cred. The second attempt, if allowed, will + * obtained a new TGT here and a new handle to the credential will also be + * obtained in ads_acquire_cred. LDAP SASL bind is used to send and receive + * the GSS tokens to and from the ADS server. + * Please look at ads_bind for more information. + * Paramters: + * ah : handle to ADS server + * cred_handle : handle to administrative user's credential (TGT) + * oid : Kerberos 5 object identifier + * kinit_retry : if 0 then a second attempt can be made to establish a + * security context with ADS server if first attempt fails + * Returns: + * gss_context : security context to ADS server + * sercred : encrypted ADS server's supported security layers + * do_acquire_cred: if 1 then a second attempt will be made to establish a + * security context with ADS server after getting a new + * handle to the user's credential + * kinit_retry : if 1 then a second attempt will be made to establish a + * a security context and no further attempts can be made + * -1 : error + * 0 : success + */ +static int +ads_establish_sec_context(ADS_HANDLE *ah, gss_ctx_id_t *gss_context, + gss_cred_id_t cred_handle, gss_OID oid, struct berval **sercred, + int *kinit_retry, int *do_acquire_cred) +{ + OM_uint32 maj, min, time_rec; + char service_name[ADS_MAXBUFLEN], *user_dn; + gss_buffer_desc send_tok, service_buf; + gss_name_t target_name; + gss_buffer_desc input; + gss_buffer_desc *inputptr; + struct berval cred; + OM_uint32 ret_flags; + int stat, len; + int gss_flags; + + /* + * 6 additional bytes for the "cn=,, " and the null terminator + */ + len = strlen(ah->user) + strlen(ah->container) + + strlen(ah->domain_dn) + 6; + + if ((user_dn = (char *)malloc(len)) == NULL) + return (-1); + + (void) snprintf(user_dn, len, "cn=%s,%s,%s", ah->user, ah->container, + ah->domain_dn); + + (void) snprintf(service_name, ADS_MAXBUFLEN, "ldap@%s", ah->hostname); + service_buf.value = service_name; + service_buf.length = strlen(service_name)+1; + if ((maj = gss_import_name(&min, &service_buf, + (gss_OID) gss_nt_service_name, + &target_name)) != GSS_S_COMPLETE) { + ads_display_stat(maj, min); + (void) gss_release_oid(&min, &oid); + free(user_dn); + return (-1); + } + + *gss_context = GSS_C_NO_CONTEXT; + *sercred = NULL; + inputptr = GSS_C_NO_BUFFER; + gss_flags = GSS_C_MUTUAL_FLAG; + do { + if (krb5_establish_sec_ctx_kinit(ah->user, ah->pwd, + cred_handle, gss_context, target_name, oid, + gss_flags, inputptr, &send_tok, + &ret_flags, &time_rec, kinit_retry, + do_acquire_cred, &maj, "ads") == -1) { + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + free(user_dn); + return (-1); + } + + cred.bv_val = send_tok.value; + cred.bv_len = send_tok.length; + if (*sercred) { + ber_bvfree(*sercred); + *sercred = NULL; + } + stat = ldap_sasl_bind_s(ah->ld, user_dn, "GSSAPI", + &cred, NULL, NULL, sercred); + if (stat != LDAP_SUCCESS && + stat != LDAP_SASL_BIND_IN_PROGRESS) { + /* LINTED - E_SEC_PRINTF_VAR_FMT */ + syslog(LOG_ERR, ldap_err2string(stat)); + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + (void) gss_release_buffer(&min, &send_tok); + free(user_dn); + return (-1); + } + input.value = (*sercred)->bv_val; + input.length = (*sercred)->bv_len; + inputptr = &input; + if (send_tok.length > 0) + (void) gss_release_buffer(&min, &send_tok); + } while (maj != GSS_S_COMPLETE); + + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + free(user_dn); + + return (0); +} + +/* + * ads_negotiate_sec_layer + * Call by ads_bind() to negotiate additional security layer for further + * communication after security context establishment. No additional security + * is needed so a "no security layer" is negotiated. The security layer is + * described in the SASL RFC 2478 and this step is needed for secure LDAP + * binding. LDAP SASL bind is used to send and receive the GSS tokens to and + * from the ADS server. + * Please look at ads_bind for more information. + * + * Paramters: + * ah : handle to ADS server + * gss_context: security context to ADS server + * sercred : encrypted ADS server's supported security layers + * Returns: + * -1 : error + * 0 : success + */ +static int +ads_negotiate_sec_layer(ADS_HANDLE *ah, gss_ctx_id_t gss_context, + struct berval *sercred) +{ + OM_uint32 maj, min; + gss_buffer_desc unwrap_inbuf, unwrap_outbuf; + gss_buffer_desc wrap_inbuf, wrap_outbuf; + int conf_state, sec_layer; + char auth_id[5]; + struct berval cred; + int stat; + gss_qop_t qt; + + /* check for server supported security layer */ + unwrap_inbuf.value = sercred->bv_val; + unwrap_inbuf.length = sercred->bv_len; + if ((maj = gss_unwrap(&min, gss_context, + &unwrap_inbuf, &unwrap_outbuf, + &conf_state, &qt)) != GSS_S_COMPLETE) { + ads_display_stat(maj, min); + if (sercred) + ber_bvfree(sercred); + return (-1); + } + sec_layer = *((char *)unwrap_outbuf.value); + (void) gss_release_buffer(&min, &unwrap_outbuf); + if (!(sec_layer & 1)) { + syslog(LOG_ERR, "smb_ads: ADS server does not support " + "no security layer!\n"); + if (sercred) ber_bvfree(sercred); + return (-1); + } + if (sercred) ber_bvfree(sercred); + + /* no security layer needed after successful binding */ + auth_id[0] = 0x01; + + /* byte 2-4: max client recv size in network byte order */ + auth_id[1] = 0x00; + auth_id[2] = 0x40; + auth_id[3] = 0x00; + wrap_inbuf.value = auth_id; + wrap_inbuf.length = 4; + conf_state = 0; + if ((maj = gss_wrap(&min, gss_context, conf_state, 0, &wrap_inbuf, + &conf_state, &wrap_outbuf)) != GSS_S_COMPLETE) { + ads_display_stat(maj, min); + return (-1); + } + + cred.bv_val = wrap_outbuf.value; + cred.bv_len = wrap_outbuf.length; + sercred = NULL; + stat = ldap_sasl_bind_s(ah->ld, NULL, "GSSAPI", &cred, NULL, NULL, + &sercred); + if (stat != LDAP_SUCCESS && stat != LDAP_SASL_BIND_IN_PROGRESS) { + /* LINTED - E_SEC_PRINTF_VAR_FMT */ + syslog(LOG_ERR, ldap_err2string(stat)); + (void) gss_release_buffer(&min, &wrap_outbuf); + return (-1); + } + + (void) gss_release_buffer(&min, &wrap_outbuf); + if (sercred) + ber_bvfree(sercred); + + return (0); +} + +/* + * ads_bind + * Use secure binding to bind to ADS server. + * Use GSS-API with Kerberos 5 as the security mechanism and LDAP SASL with + * Kerberos 5 as the security mechanisn to authenticate, obtain a security + * context, and securely bind an administrative user so that other LDAP + * commands can be used, i.e. add and delete. + * + * To obtain the security context, a Kerberos ticket-granting ticket (TGT) + * for the user is needed to obtain a ticket for the LDAP service. To get + * a TGT for the user, the username and password is needed. Once a TGT is + * obtained then it will be stored locally and used until it is expired. + * This routine will automatically obtained a TGT for the first time or when + * it expired. LDAP SASL bind is then finally used to send GSS tokens to + * obtain a security context for the LDAP service on the ADS server. If + * there is any problem getting the security context then a new TGT will be + * obtain to try getting the security context once more. + * + * After the security context is obtain and established, the LDAP SASL bind + * is used to negotiate an additional security layer. No further security is + * needed so a "no security layer" is negotiated. After this the security + * context can be deleted and further LDAP commands can be sent to the ADS + * server until a LDAP unbind command is issued to the ADS server. + * Paramaters: + * ah: handle to ADS server + * Returns: + * -1: error + * 0: success + */ +static int +ads_bind(ADS_HANDLE *ah) +{ + OM_uint32 min; + gss_cred_id_t cred_handle; + gss_ctx_id_t gss_context; + OM_uint32 maj; + gss_OID oid; + struct berval *sercred; + int kinit_retry, do_acquire_cred; + + kinit_retry = 0; + do_acquire_cred = 0; + acquire_cred: + + if (ads_acquire_cred(ah, &cred_handle, &oid, &kinit_retry)) + return (-1); + + if (ads_establish_sec_context(ah, &gss_context, cred_handle, + oid, &sercred, &kinit_retry, &do_acquire_cred)) { + (void) gss_release_cred(&min, &cred_handle); + if (do_acquire_cred) { + do_acquire_cred = 0; + goto acquire_cred; + } + return (-1); + } + + if (ads_negotiate_sec_layer(ah, gss_context, sercred)) { + (void) gss_release_cred(&min, &cred_handle); + (void) gss_delete_sec_context(&min, &gss_context, NULL); + return (-1); + } + + if ((maj = gss_release_cred(&min, &cred_handle)) + != GSS_S_COMPLETE) { + syslog(LOG_ERR, "smb_ads: Can't release credential handle\n"); + ads_display_stat(maj, min); + (void) gss_delete_sec_context(&min, &gss_context, NULL); + return (-1); + } + + if ((maj = gss_delete_sec_context(&min, &gss_context, NULL)) + != GSS_S_COMPLETE) { + syslog(LOG_ERR, "smb_ads: Can't delete security context\n"); + ads_display_stat(maj, min); + return (-1); + } + + return (0); +} + +/* + * ads_add_share + * Call by ads_publish_share to create share object in ADS. + * This routine specifies the attributes of an ADS LDAP share object. The first + * attribute and values define the type of ADS object, the share object. The + * second attribute and value define the UNC of the share data for the share + * object. The LDAP synchronous add command is used to add the object into ADS. + * The container location to add the object needs to specified. + * Parameters: + * ah : handle to ADS server + * adsShareName: name of share object to be created in ADS + * shareUNC : share name on NetForce + * adsContainer: location in ADS to create share object + * + * Returns: + * -1 : error + * 0 : success + */ +int +ads_add_share(ADS_HANDLE *ah, const char *adsShareName, + const char *unc_name, const char *adsContainer) +{ + LDAPMod *addattrs[3]; + char *tmp1[5], *tmp2[5]; + int j = -1; + char *share_dn; + char buf[ADS_MAXMSGLEN]; + + int len, ret; + + len = 5 + strlen(adsShareName) + strlen(adsContainer) + + strlen(ah->domain_dn) + 1; + + share_dn = (char *)malloc(len); + if (share_dn == NULL) + return (-1); + + (void) snprintf(share_dn, len, "cn=%s,%s,%s", adsShareName, + adsContainer, ah->domain_dn); + + addattrs[++j] = (LDAPMod *)malloc(sizeof (LDAPMod)); + addattrs[j]->mod_op = LDAP_MOD_ADD; + addattrs[j]->mod_type = "objectClass"; + tmp1[0] = "top"; + tmp1[1] = "leaf"; + tmp1[2] = "connectionPoint"; + tmp1[3] = "volume"; + tmp1[4] = 0; + addattrs[j]->mod_values = tmp1; + + addattrs[++j] = (LDAPMod *) malloc(sizeof (LDAPMod)); + addattrs[j]->mod_op = LDAP_MOD_ADD; + addattrs[j]->mod_type = "uNCName"; + + tmp2[0] = (char *)unc_name; + tmp2[1] = 0; + addattrs[j]->mod_values = tmp2; + + addattrs[++j] = 0; + + if ((ret = ldap_add_s(ah->ld, share_dn, addattrs)) != LDAP_SUCCESS) { + (void) snprintf(buf, ADS_MAXMSGLEN, + "ads_add_share: %s:", share_dn); + /* LINTED - E_SEC_PRINTF_VAR_FMT */ + syslog(LOG_ERR, ldap_err2string(ret)); + free_attr(addattrs); + free(share_dn); + return (ret); + } + free(share_dn); + free_attr(addattrs); + + (void) snprintf(buf, ADS_MAXMSGLEN, + "Share %s has been added to ADS container: %s.\n", adsShareName, + adsContainer); + syslog(LOG_DEBUG, "smb_ads: %s", buf); + + return (0); +} + +/* + * ads_del_share + * Call by ads_remove_share to remove share object from ADS. The container + * location to remove the object needs to specified. The LDAP synchronous + * delete command is used. + * Parameters: + * ah : handle to ADS server + * adsShareName: name of share object in ADS to be removed + * adsContainer: location of share object in ADS + * Returns: + * -1 : error + * 0 : success + */ +static int +ads_del_share(ADS_HANDLE *ah, const char *adsShareName, + const char *adsContainer) +{ + char *share_dn, buf[ADS_MAXMSGLEN]; + int len, ret; + + len = 5 + strlen(adsShareName) + strlen(adsContainer) + + strlen(ah->domain_dn) + 1; + + share_dn = (char *)malloc(len); + if (share_dn == NULL) + return (-1); + + (void) snprintf(share_dn, len, "cn=%s,%s,%s", adsShareName, + adsContainer, ah->domain_dn); + if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) { + /* LINTED - E_SEC_PRINTF_VAR_FMT */ + syslog(LOG_ERR, ldap_err2string(ret)); + free(share_dn); + return (-1); + } + free(share_dn); + + (void) snprintf(buf, ADS_MAXMSGLEN, + "Share %s has been removed from ADS container: %s.\n", + adsShareName, adsContainer); + syslog(LOG_DEBUG, "smb_ads: %s", buf); + + return (0); +} + + +/* + * ads_escape_search_filter_chars + * + * This routine will escape the special characters found in a string + * that will later be passed to the ldap search filter. + * + * RFC 1960 - A String Representation of LDAP Search Filters + * 3. String Search Filter Definition + * If a value must contain one of the characters '*' OR '(' OR ')', + * these characters + * should be escaped by preceding them with the backslash '\' character. + * + * RFC 2252 - LDAP Attribute Syntax Definitions + * a backslash quoting mechanism is used to escape + * the following separator symbol character (such as "'", "$" or "#") if + * it should occur in that string. + */ +static int +ads_escape_search_filter_chars(const char *src, char *dst) +{ + int avail = ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */ + + if (src == NULL || dst == NULL) + return (-1); + + while (*src) { + if (!avail) { + *dst = 0; + return (-1); + } + + switch (*src) { + case '\\': + case '\'': + case '$': + case '#': + case '*': + case '(': + case ')': + *dst++ = '\\'; + avail--; + /* fall through */ + + default: + *dst++ = *src++; + avail--; + } + } + + *dst = 0; + + return (0); +} + +/* + * ads_lookup_share + * The search filter is set to search for a specific share name in the + * specified ADS container. The LDSAP synchronous search command is used. + * Parameters: + * ah : handle to ADS server + * adsShareName: name of share object in ADS to be searched + * adsContainer: location of share object in ADS + * Returns: + * -1 : error + * 0 : not found + * 1 : found + */ +int +ads_lookup_share(ADS_HANDLE *ah, const char *adsShareName, + const char *adsContainer, char *unc_name) +{ + char *attrs[4], filter[ADS_MAXBUFLEN]; + char *share_dn; + int len, ret; + LDAPMessage *res; + char tmpbuf[ADS_MAXBUFLEN]; + + if (adsShareName == NULL || adsContainer == NULL) + return (-1); + + len = 5 + strlen(adsShareName) + strlen(adsContainer) + + strlen(ah->domain_dn) + 1; + + share_dn = (char *)malloc(len); + if (share_dn == NULL) + return (-1); + + (void) snprintf(share_dn, len, "cn=%s,%s,%s", adsShareName, + adsContainer, ah->domain_dn); + + res = NULL; + attrs[0] = "cn"; + attrs[1] = "objectClass"; + attrs[2] = "uNCName"; + attrs[3] = NULL; + + if (ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) { + free(share_dn); + return (-1); + } + + (void) snprintf(filter, sizeof (filter), + "(&(objectClass=volume)(uNCName=%s))", tmpbuf); + + if ((ret = ldap_search_s(ah->ld, share_dn, + LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) { + /* LINTED - E_SEC_PRINTF_VAR_FMT */ + syslog(LOG_ERR, ldap_err2string(ret)); + (void) ldap_msgfree(res); + free(share_dn); + return (0); + } + + (void) free(share_dn); + + /* no match is found */ + if (ldap_count_entries(ah->ld, res) == 0) { + (void) ldap_msgfree(res); + return (0); + } + + /* free the search results */ + (void) ldap_msgfree(res); + + return (1); +} + +/* + * ads_convert_directory + * Convert relative share directory to UNC to be appended to hostname. + * i.e. cvol/a/b -> cvol\a\b + */ +char * +ads_convert_directory(char *rel_dir) +{ + char *t, *s2; + int len; + + if (rel_dir == NULL) + return (NULL); + + len = strlen(rel_dir) + 1; + s2 = (char *)malloc(len); + if (s2 == NULL) + return (NULL); + + t = s2; + while (*rel_dir) { + if (*rel_dir == '/') { + *t++ = '\\'; + rel_dir++; + } else { + *t++ = *rel_dir++; + } + } + *t = '\0'; + return (s2); +} + +/* + * ads_publish_share + * Publish share into ADS. If a share name already exist in ADS in the same + * container then the existing share object is removed before adding the new + * share object. + * Parameters: + * ah : handle return from ads_open + * adsShareName: name of share to be added to ADS directory + * shareUNC : name of share on client, can be NULL to use the same name + * as adsShareName + * adsContainer: location for share to be added in ADS directory, ie + * ou=share_folder + * uncType : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR + * to use host ip addr for UNC. + * Returns: + * -1 : error + * 0 : success + */ +int +ads_publish_share(ADS_HANDLE *ah, const char *adsShareName, + const char *shareUNC, const char *adsContainer, const char *hostname) +{ + int ret; + char unc_name[ADS_MAXBUFLEN]; + + if (adsShareName == NULL || adsContainer == NULL) + return (-1); + + if (shareUNC == 0 || *shareUNC == 0) + shareUNC = adsShareName; + + if (ads_build_unc_name(unc_name, sizeof (unc_name), + hostname, shareUNC) < 0) { + syslog(LOG_DEBUG, "smb_ads: Cannot publish share '%s' " + "[missing UNC name]", shareUNC); + return (-1); + } + + ret = ads_lookup_share(ah, adsShareName, adsContainer, unc_name); + + switch (ret) { + case 1: + (void) ads_del_share(ah, adsShareName, adsContainer); + ret = ads_add_share(ah, adsShareName, unc_name, adsContainer); + break; + + case 0: + ret = ads_add_share(ah, adsShareName, unc_name, adsContainer); + if (ret == LDAP_ALREADY_EXISTS) { + syslog(LOG_DEBUG, "smb_ads: Cannot publish share '%s' " + "[name is already in use]", adsShareName); + ret = -1; + } + break; + + case -1: + default: + /* return with error code */ + ret = -1; + } + + return (ret); +} + +/* + * ads_remove_share + * Remove share from ADS. A search is done first before explicitly removing + * the share. + * Parameters: + * ah : handle return from ads_open + * adsShareName: name of share to be removed from ADS directory + * adsContainer: location for share to be removed from ADS directory, ie + * ou=share_folder + * Returns: + * -1 : error + * 0 : success + */ +int +ads_remove_share(ADS_HANDLE *ah, const char *adsShareName, const char *shareUNC, + const char *adsContainer, const char *hostname) +{ + int ret; + char unc_name[ADS_MAXBUFLEN]; + + if (adsShareName == NULL || adsContainer == NULL) + return (-1); + if (shareUNC == 0 || *shareUNC == 0) + shareUNC = adsShareName; + + if (ads_build_unc_name(unc_name, sizeof (unc_name), + hostname, shareUNC) < 0) { + syslog(LOG_DEBUG, "smb_ads: Unable to remove share '%s' from " + "ADS [missing UNC name]", shareUNC); + return (-1); + } + + ret = ads_lookup_share(ah, adsShareName, adsContainer, unc_name); + if (ret == 0) + return (0); + if (ret == -1) + return (-1); + + return (ads_del_share(ah, adsShareName, adsContainer)); +} + +/* + * ads_get_computer_dn + * + * Build the distinguish name for this system. + */ +static void +ads_get_computer_dn(ADS_HANDLE *ah, char *buf, size_t buflen) +{ + char hostname[MAXHOSTNAMELEN]; + + (void) smb_gethostname(hostname, MAXHOSTNAMELEN, 0); + (void) snprintf(buf, buflen, "cn=%s,cn=%s,%s", + hostname, ADS_COMPUTERS_CN, ah->domain_dn); +} + +static char * +ads_get_host_principal(char *fqhost) +{ + int len; + char *princ; + + if (!fqhost) + return (NULL); + + len = strlen(ADS_HOST_PREFIX) + strlen(fqhost) + 1; + princ = (char *)malloc(len); + + if (!princ) { + syslog(LOG_ERR, "ads_get_host_principal: resource shortage"); + return (NULL); + } + (void) snprintf(princ, len, "%s%s", ADS_HOST_PREFIX, + fqhost); + + return (princ); +} + +static char * +ads_get_host_principal_w_realm(char *princ, char *domain) +{ + int len; + char *realm; + char *princ_r; + + if (!princ || !domain) + return (NULL); + + realm = strdup(domain); + if (!realm) + return (NULL); + + (void) utf8_strupr(realm); + + len = strlen(princ) + 1 + strlen(realm) + 1; + princ_r = (char *)malloc(len); + if (!princ_r) { + syslog(LOG_ERR, "ads_get_host_principal_w_realm: resource" + " shortage"); + free(realm); + return (NULL); + } + + (void) snprintf(princ_r, len, "%s@%s", princ, realm); + free(realm); + + return (princ_r); +} + +/* + * ads_get_host_principals + * + * If fqhost is NULL, this function will attempt to obtain fully qualified + * hostname prior to generating the host principals. + */ +static int +ads_get_host_principals(char *fqhost, char *domain, char **princ, + char **princ_r) +{ + char hostname[MAXHOSTNAMELEN]; + + *princ = *princ_r = NULL; + + if (fqhost) { + (void) strlcpy(hostname, fqhost, MAXHOSTNAMELEN); + } else { + if (smb_getfqhostname(hostname, MAXHOSTNAMELEN) != 0) + return (-1); + } + + if ((*princ = ads_get_host_principal(hostname)) == NULL) { + return (-1); + } + + *princ_r = ads_get_host_principal_w_realm(*princ, domain); + if (*princ_r == NULL) { + free(*princ); + return (-1); + } + + return (0); +} + +/* + * ads_add_computer + * + * Returns 0 upon success. Otherwise, returns -1. + */ +static int +ads_add_computer(ADS_HANDLE *ah) +{ + LDAPMod *addattrs[7]; + char *oc_vals[6], *sam_val[2], *usr_val[2]; + char *svc_val[2], *ctl_val[2], *fqh_val[2]; + int j = -1; + int ret, usrctl_flags = 0; + char sam_acct[MAXHOSTNAMELEN + 1]; + char fqhost[MAXHOSTNAMELEN]; + char dn[ADS_DN_MAX]; + char *user_principal, *svc_principal; + char usrctl_buf[16]; + + if (smb_getfqhostname(fqhost, MAXHOSTNAMELEN) != 0) + return (-1); + + if (smb_gethostname(sam_acct, MAXHOSTNAMELEN, 0) != 0) + return (-1); + + (void) strlcat(sam_acct, "$", MAXHOSTNAMELEN + 1); + + if (ads_get_host_principals(fqhost, ah->domain, &svc_principal, + &user_principal) == -1) { + syslog(LOG_ERR, + "ads_add_computer: unable to get host principal"); + return (-1); + } + + ads_get_computer_dn(ah, dn, ADS_DN_MAX); + + addattrs[++j] = (LDAPMod *)malloc(sizeof (LDAPMod)); + addattrs[j]->mod_op = LDAP_MOD_ADD; + addattrs[j]->mod_type = "objectClass"; + oc_vals[0] = "top"; + oc_vals[1] = "person"; + oc_vals[2] = "organizationalPerson"; + oc_vals[3] = "user"; + oc_vals[4] = "computer"; + oc_vals[5] = 0; + addattrs[j]->mod_values = oc_vals; + + addattrs[++j] = (LDAPMod *) malloc(sizeof (LDAPMod)); + addattrs[j]->mod_op = LDAP_MOD_ADD; + addattrs[j]->mod_type = "sAMAccountName"; + + sam_val[0] = sam_acct; + sam_val[1] = 0; + addattrs[j]->mod_values = sam_val; + + + addattrs[++j] = (LDAPMod *) malloc(sizeof (LDAPMod)); + addattrs[j]->mod_op = LDAP_MOD_ADD; + addattrs[j]->mod_type = "userPrincipalName"; + + usr_val[0] = user_principal; + usr_val[1] = 0; + addattrs[j]->mod_values = usr_val; + + addattrs[++j] = (LDAPMod *) malloc(sizeof (LDAPMod)); + addattrs[j]->mod_op = LDAP_MOD_ADD; + addattrs[j]->mod_type = "servicePrincipalName"; + + svc_val[0] = svc_principal; + svc_val[1] = 0; + addattrs[j]->mod_values = svc_val; + + addattrs[++j] = (LDAPMod *) malloc(sizeof (LDAPMod)); + addattrs[j]->mod_op = LDAP_MOD_ADD; + addattrs[j]->mod_type = "userAccountControl"; + + usrctl_flags |= (ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT | + ADS_USER_ACCT_CTL_PASSWD_NOTREQD | + ADS_USER_ACCT_CTL_ACCOUNTDISABLE); + + (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags); + ctl_val[0] = usrctl_buf; + ctl_val[1] = 0; + addattrs[j]->mod_values = ctl_val; + addattrs[++j] = (LDAPMod *) malloc(sizeof (LDAPMod)); + addattrs[j]->mod_op = LDAP_MOD_ADD; + addattrs[j]->mod_type = "dNSHostName"; + + fqh_val[0] = fqhost; + fqh_val[1] = 0; + addattrs[j]->mod_values = fqh_val; + addattrs[++j] = 0; + + if ((ret = ldap_add_s(ah->ld, dn, addattrs)) != LDAP_SUCCESS) { + syslog(LOG_ERR, "ads_add_computer: %s", ldap_err2string(ret)); + ret = -1; + } + + free_attr(addattrs); + free(user_principal); + free(svc_principal); + + return (ret); +} + +/* + * Delete an ADS computer account. + */ +static void +ads_del_computer(ADS_HANDLE *ah) +{ + char dn[ADS_DN_MAX]; + int rc; + + ads_get_computer_dn(ah, dn, ADS_DN_MAX); + + if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS) { + syslog(LOG_DEBUG, "ads_del_computer: %s", + ldap_err2string(rc)); + } +} + +/* + * ads_lookup_computer_n_attr + * + * Lookup the value of the specified attribute on the computer + * object. If the specified attribute can be found, its value is returned + * via 'val' parameter. + * + * 'attr' parameter can be set to NULL if you only attempt to + * see whether the computer object exists on AD or not. + * + * Return: + * 1 if both the computer and the specified attribute is found. + * 0 if either the computer or the specified attribute is not found. + * -1 on error. + */ +static int +ads_lookup_computer_n_attr(ADS_HANDLE *ah, char *attr, char **val) +{ + char *attrs[2], filter[ADS_MAXBUFLEN]; + LDAPMessage *res, *entry; + char **vals; + char tmpbuf[ADS_MAXBUFLEN]; + char my_hostname[MAXHOSTNAMELEN], sam_acct[MAXHOSTNAMELEN + 1]; + char dn[ADS_DN_MAX]; + + if (smb_gethostname(my_hostname, MAXHOSTNAMELEN, 0) != 0) + return (-1); + + (void) snprintf(sam_acct, sizeof (sam_acct), "%s$", my_hostname); + ads_get_computer_dn(ah, dn, ADS_DN_MAX); + + res = NULL; + attrs[0] = attr; + attrs[1] = NULL; + + if (ads_escape_search_filter_chars(sam_acct, tmpbuf) != 0) { + return (-1); + } + + (void) snprintf(filter, sizeof (filter), + "(&(objectClass=computer)(sAMAccountName=%s))", + tmpbuf); + + if (ldap_search_s(ah->ld, dn, LDAP_SCOPE_BASE, filter, attrs, 0, + &res) != LDAP_SUCCESS) { + (void) ldap_msgfree(res); + return (0); + } + + if (attr) { + /* no match for the specified attribute is found */ + if (ldap_count_entries(ah->ld, res) == 0) { + if (val) + *val = NULL; + + (void) ldap_msgfree(res); + return (0); + } + + entry = ldap_first_entry(ah->ld, res); + if (entry) { + vals = ldap_get_values(ah->ld, entry, attr); + if (!vals && val) { + *val = NULL; + (void) ldap_msgfree(res); + return (0); + } + + if (vals[0] != NULL && val) + *val = strdup(vals[0]); + } + } + + /* free the search results */ + (void) ldap_msgfree(res); + return (1); +} + +/* + * ads_find_computer + * + * Return: + * 1 if found. + * 0 if not found or encounters error. + */ +static int +ads_find_computer(ADS_HANDLE *ah) +{ + return (ads_lookup_computer_n_attr(ah, NULL, NULL) == 1); +} + +/* + * ads_modify_computer + * + * Modify the user account control attribute of an existing computer + * object on AD. + * + * Returns 0 on success. Otherwise, returns -1. + */ +static int +ads_modify_computer(ADS_HANDLE *ah, int des_only) +{ + LDAPMod *attrs[6]; + char *ctl_val[2]; + int j = -1; + int ret, usrctl_flags = 0; + char dn[ADS_DN_MAX]; + char usrctl_buf[16]; + + ads_get_computer_dn(ah, dn, ADS_DN_MAX); + + attrs[++j] = (LDAPMod *) malloc(sizeof (LDAPMod)); + attrs[j]->mod_op = LDAP_MOD_REPLACE; + attrs[j]->mod_type = "userAccountControl"; + + usrctl_flags |= (ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT | + ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION | + ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD); + + if (des_only) + usrctl_flags |= ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY; + + (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags); + ctl_val[0] = usrctl_buf; + ctl_val[1] = 0; + attrs[j]->mod_values = ctl_val; + + attrs[++j] = 0; + + if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) { + syslog(LOG_ERR, "ads_modify_computer: %s", + ldap_err2string(ret)); + ret = -1; + } + + free_attr(attrs); + return (ret); +} + +/* + * ads_lookup_computer_attr_kvno + * + * Lookup the value of the Kerberos version number attribute of the computer + * account. + */ +static krb5_kvno +ads_lookup_computer_attr_kvno(ADS_HANDLE *ah) +{ + char *val = NULL; + int kvno = 1; + + if (ads_lookup_computer_n_attr(ah, "ms-DS-KeyVersionNumber", + &val) == 1) { + if (val) { + kvno = atoi(val); + free(val); + } + } + + return (kvno); +} + +/* + * ads_gen_machine_passwd + * + * Returned a null-terminated machine password generated randomly + * from [0-9a-zA-Z] character set. In order to pass the password + * quality check (three character classes), an uppercase letter is + * used as the first character of the machine password. + */ +static int +ads_gen_machine_passwd(char *machine_passwd, int bufsz) +{ + char *data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJK" + "LMNOPQRSTUVWXYZ"; + int datalen = strlen(data); + int i, data_idx; + + if (!machine_passwd || bufsz == 0) + return (-1); + + /* + * The decimal value of upper case 'A' is 65. Randomly pick + * an upper-case letter from the ascii table. + */ + machine_passwd[0] = (random() % 26) + 65; + for (i = 1; i < bufsz - 1; i++) { + data_idx = random() % datalen; + machine_passwd[i] = data[data_idx]; + } + + machine_passwd[bufsz - 1] = 0; + return (0); +} + +/* + * adjoin + * + * Besides the NT-4 style domain join (using MS-RPC), CIFS server also + * provides the domain join using Kerberos Authentication, Keberos + * Change & Set password, and LDAP protocols. Basically, adjoin + * operation would require the following tickets to be acquired for the + * the user account that is provided for the domain join. + * + * 1) a Keberos TGT ticket, + * 2) a ldap service ticket, and + * 3) kadmin/changpw service ticket + * + * The ADS client first sends a ldap search request to find out whether + * or not the workstation trust account already exists in the Active Directory. + * The existing computer object for this workstation will be removed and + * a new one will be added. The machine account password is randomly + * generated and set for the newly created computer object using KPASSD + * protocol (See RFC 3244). Once the password is set, our ADS client + * finalizes the machine account by modifying the user acount control + * attribute of the computer object. Kerberos keys derived from the machine + * account password will be stored locally in /etc/krb5/krb5.keytab file. + * That would be needed while acquiring Kerberos TGT ticket for the host + * principal after the domain join operation. + */ +adjoin_status_t +adjoin(char *machine_passwd, int len) +{ + ADS_HANDLE *ah = NULL; + krb5_enctype enctypes[10]; + krb5_context ctx = NULL; + krb5_principal krb5princ; + krb5_kvno kvno; + char *princ, *princ_r; + int des_only, delete = 1, fini_krbctx = 1; + adjoin_status_t rc = ADJOIN_SUCCESS; + struct stat fstat; + + char *idmap_ccache = "/var/run/idmap/ccache"; + + if ((ah = ads_open()) == NULL) + return (ADJOIN_ERR_GET_HANDLE); + + if (ads_gen_machine_passwd(machine_passwd, len) != 0) { + ads_close(ah); + return (ADJOIN_ERR_GEN_PASSWD); + } + + if (ads_find_computer(ah)) + ads_del_computer(ah); + + if (ads_add_computer(ah) != 0) { + ads_close(ah); + return (ADJOIN_ERR_ADD_TRUST_ACCT); + } + + /* + * Call library functions that can be used to get + * the list of encryption algorithms available on the system. + * (similar to what 'encrypt -l' CLI does). For now, + * unless someone has modified the configuration of the + * cryptographic framework (very unlikely), the following is the + * list of algorithms available on any system running Nevada + * by default. + */ + enctypes[0] = ENCTYPE_DES_CBC_CRC; + enctypes[1] = ENCTYPE_DES_CBC_MD5; + enctypes[2] = ENCTYPE_ARCFOUR_HMAC; + enctypes[3] = ENCTYPE_AES128_CTS_HMAC_SHA1_96; + + des_only = 0; + + /* + * If we are talking to a Longhorn server, we need to set up + * the msDS-SupportedEncryptionTypes attribute of the computer + * object accordingly + * + * The code to modify the msDS-SupportedEncryptionTypes can be + * added once we figure out why the Longhorn server rejects the + * SmbSessionSetup request sent by SMB redirector. + */ + + if (ads_get_host_principals(NULL, ah->domain, &princ, &princ_r) == -1) { + ads_del_computer(ah); + ads_close(ah); + return (ADJOIN_ERR_GET_HOST_PRINC); + } + + if (smb_krb5_ctx_init(&ctx) != 0) { + fini_krbctx = 0; + rc = ADJOIN_ERR_INIT_KRB_CTX; + goto adjoin_cleanup; + } + + if (smb_krb5_get_principal(ctx, princ_r, &krb5princ) != 0) { + rc = ADJOIN_ERR_GET_KRB_PRINC; + goto adjoin_cleanup; + } + + if (smb_krb5_setpwd(ctx, krb5princ, machine_passwd) != 0) { + rc = ADJOIN_ERR_KSETPWD; + goto adjoin_cleanup; + } + + kvno = ads_lookup_computer_attr_kvno(ah); + if (ads_modify_computer(ah, des_only) != 0) { + rc = ADJOIN_ERR_MOD_TRUST_ACCT; + goto adjoin_cleanup; + } + if (smb_krb5_write_keytab(ctx, krb5princ, "/etc/krb5/krb5.keytab", kvno, + machine_passwd, enctypes, 4) != 0) { + rc = ADJOIN_ERR_WRITE_KEYTAB; + goto adjoin_cleanup; + } + + /* Set IDMAP config */ + if (smb_config_set_idmap_domain(ah->domain) != 0) { + rc = ADJOIN_ERR_IDMAP_SET_DOMAIN; + goto adjoin_cleanup; + } + + if (smb_config_set_idmap_gc(ah->hostname) != 0) { + rc = ADJOIN_ERR_IDMAP_SET_GC; + goto adjoin_cleanup; + } + + /* Refresh IDMAP service */ + if (smb_config_refresh_idmap() != 0) { + rc = ADJOIN_ERR_IDMAP_REFRESH; + goto adjoin_cleanup; + } + + /* Remove the idmap ccache */ + if (stat(idmap_ccache, &fstat) == 0) { + if (remove(idmap_ccache) != 0) { + rc = ADJOIN_ERR_IDMAP_CCACHE; + goto adjoin_cleanup; + } + } + + delete = 0; +adjoin_cleanup: + if (delete) + ads_del_computer(ah); + + if (fini_krbctx) + smb_krb5_ctx_fini(ctx); + + ads_close(ah); + free(princ); + free(princ_r); + + return (rc); +} + +/* + * adjoin_report_err + * + * Display error message for the specific adjoin error code. + */ +char * +adjoin_report_err(adjoin_status_t status) +{ + if (status < 0 || status >= ADJOIN_NUM_STATUS) + return ("ADJOIN: unknown status"); + + return (adjoin_errmsg[status]); +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.h b/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.h new file mode 100644 index 0000000000..af52f0ebfc --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_ads.h @@ -0,0 +1,87 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SMBSRV_ADS_H +#define _SMBSRV_ADS_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <stdlib.h> +#include <netdb.h> +#include <smbsrv/libsmbns.h> +#include <smbsrv/string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * UserAccountControl flags: manipulate user account properties. + * + * The hexadecimal value of the following property flags are based on MSDN + * article # 305144. + */ +#define ADS_USER_ACCT_CTL_SCRIPT 0x00000001 +#define ADS_USER_ACCT_CTL_ACCOUNTDISABLE 0x00000002 +#define ADS_USER_ACCT_CTL_HOMEDIR_REQUIRED 0x00000008 +#define ADS_USER_ACCT_CTL_LOCKOUT 0x00000010 +#define ADS_USER_ACCT_CTL_PASSWD_NOTREQD 0x00000020 +#define ADS_USER_ACCT_CTL_PASSWD_CANT_CHANGE 0x00000040 +#define ADS_USER_ACCT_CTL_ENCRYPTED_TEXT_PWD_ALLOWED 0x00000080 +#define ADS_USER_ACCT_CTL_TMP_DUP_ACCT 0x00000100 +#define ADS_USER_ACCT_CTL_NORMAL_ACCT 0x00000200 +#define ADS_USER_ACCT_CTL_INTERDOMAIN_TRUST_ACCT 0x00000800 +#define ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT 0x00001000 +#define ADS_USER_ACCT_CTL_SRV_TRUST_ACCT 0x00002000 +#define ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD 0x00010000 +#define ADS_USER_ACCT_CTL_MNS_LOGON_ACCT 0x00020000 +#define ADS_USER_ACCT_CTL_SMARTCARD_REQUIRED 0x00040000 +#define ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION 0x00080000 +#define ADS_USER_ACCT_CTL_NOT_DELEGATED 0x00100000 +#define ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY 0x00200000 +#define ADS_USER_ACCT_CTL_DONT_REQ_PREAUTH 0x00400000 +#define ADS_USER_ACCT_CTL_PASSWD_EXPIRED 0x00800000 +#define ADS_USER_ACCT_CTL_TRUSTED_TO_AUTH_FOR_DELEGATION 0x01000000 + +typedef struct ads_host_info_s { + char *name; /* fully qualified hostname */ + int port; /* ldap port */ + in_addr_t ip_addr; /* network byte order */ +} ADS_HOST_INFO; + +#define UNC_HOSTADDR 0 /* use ip addr in UNC */ +#define UNC_HOSTNAME 1 /* use hostname in UNC */ +#define ADS_PATH_SCRN_LEN 60 + +ADS_HOST_INFO *ads_find_host(char *, char *, int *, char *, int *); +char *ads_convert_directory(char *); + + +#ifdef __cplusplus +} +#endif + +#endif /* _SMBSRV_ADS_H */ diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_browser.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_browser.c new file mode 100644 index 0000000000..853d1ff814 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_browser.c @@ -0,0 +1,1410 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <syslog.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <synch.h> +#include <netdb.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include <smbsrv/libsmbns.h> + +#include <smbsrv/cifs.h> +#include <smbsrv/mailslot.h> + +#include <smbns_browser.h> +#include <smbns_netbios.h> + +#define SMB_SERVER_SIGNATURE 0xaa550415 + +/* + * Macro definitions: + */ +static char *lanman = MAILSLOT_LANMAN; +static char *browse = MAILSLOT_BROWSE; + +typedef struct server_info { + uint32_t type; + uint32_t signature; + char major; + char minor; + char hostname[NETBIOS_NAME_SZ]; + char comment[SMB_PI_MAX_COMMENT]; + char update_count; + struct name_entry name; +} server_info_t; + +#define BROWSER_NF_INVALID 0x00 +#define BROWSER_NF_VALID 0x01 + +typedef struct browser_netinfo { + uint32_t flags; + int next_announce; + int reps; + int interval; + server_info_t server; + mutex_t mtx; +} browser_netinfo_t; + +/* + * Local Data Definitions: + */ +static struct browser_netinfo smb_browser_info[SMB_PI_MAX_NETWORKS]; + +static void smb_browser_init(void); + +static inline browser_netinfo_t * +smb_browser_getnet(int net) +{ + browser_netinfo_t *subnet; + + if (net < smb_nic_get_num()) { + subnet = &smb_browser_info[net]; + (void) mutex_lock(&subnet->mtx); + if (subnet->flags & BROWSER_NF_VALID) + return (subnet); + } + + return (0); +} + +static inline void +smb_browser_putnet(browser_netinfo_t *netinfo) +{ + if (netinfo) + (void) mutex_unlock(&netinfo->mtx); +} + +/* + * 3. Browser Overview + * + * Hosts involved in the browsing process can be separated into two + * distinct groups, browser clients and browser servers (often referred to + * simply as "browsers"). + * + * A browser is a server which maintains information about servers - + * primarily the domain they are in and the services that they are running + * -- and about domains. Browsers may assume several different roles in + * their lifetimes, and dynamically switch between them. + * + * Browser clients are of two types: workstations and (non-browser) + * servers. In the context of browsing, workstations query browsers for the + * information they contain; servers supply browsers the information by + * registering with them. Note that, at times, browsers may themselves + * behave as browser clients and query other browsers. + * + * For the purposes of this specification, a domain is simply a name with + * which to associate a group of resources such as computers, servers and + * users. Domains allow a convenient means for browser clients to restrict + * the scope of a search when they query browser servers. Every domain has + * a "master" server called the Primary Domain Controller (PDC) that + * manages various activities within the domain. + * + * One browser for each domain on a subnet is designated the Local Master + * Browser for that domain. Servers in its domain on the subnet register + * with it, as do the Local Master Browsers for other domains on the + * subnet. It uses these registrations to maintain authoritative + * information about its domain on its subnet. If there are other subnets + * in the network, it also knows the name of the server running the + * domain's Domain Master Browser; it registers with it, and uses it to + * obtain information about the rest of the network (see below). + * + * Clients on a subnet query browsers designated as the Backup Browsers for + * the subnet (not the Master Browser). Backup Browsers maintain a copy of + * the information on the Local Master Browser; they get it by periodically + * querying the Local Master Browser for all of its information. Clients + * find the Backup Browsers by asking the Local Master Browser. Clients are + * expected to spread their queries evenly across Backup Browsers to + * balance the load. + * + * The Local Master Browser is dynamically elected automatically. Multiple + * Backup Browser Servers may exist per subnet; they are selected from + * among the potential browser servers by the Local Master Browser, which + * is configured to select enough to handle the expected query load. + * + * When there are multiple subnets, a Domain Master Browser is assigned + * the task of keeping the multiple subnets in synchronization. The Primary + * Domain Controller (PDC) always acts as the Domain Master Browser. The + * Domain Master Browser periodically acts as a client and queries all the + * Local Master Browsers for its domain, asking them for a list containing + * all the domains and all the servers in their domain known within their + * subnets; it merges all the replies into a single master list. This + * allows a Domain Master Browser server to act as a collection point for + * inter-subnet browsing information. Local Master Browsers periodically + * query the Domain Master Browser to retrieve the network-wide information + * it maintains. + * + * When a domain spans only a single subnet, there will not be any distinct + * Local Master Browser; this role will be handled by the Domain Master + * Browser. Similarly, the Domain Master Browser is always the Local Master + * Browser for the subnet it is on. + * + * When a browser client suspects that the Local Master Browser has failed, + * the client will instigate an election in which the browser servers + * participate, and some browser servers may change roles. + * + * Some characteristics of a good browsing mechanism include: + * . minimal network traffic + * . minimum server discovery time + * . minimum change discovery latency + * . immunity to machine failures + * + * Historically, Browser implementations had been very closely tied to + * NETBIOS and datagrams. The early implementations caused a lot of + * broadcast traffic. See Appendix D for an overview that presents how the + * Browser specification evolved. + * + * 4. Browsing Protocol Architecture + * + * This section first describes the how the browsing protocol is layered, + * then describes the roles of clients, servers, and browsers in the + * browsing subsystem. + * + * 4.1 Layering of Browsing Protocol Requests + * + * Most of the browser functionality is implemented using mailslots. + * Mailslots provide a mechanism for fast, unreliable unidirectional data + * transfer; they are named via ASCII "mailslot (path) name". Mailslots are + * implemented using the CIFS Transact SMB which is encapsulated in a + * NETBIOS datagram. Browser protocol requests are sent to browser specific + * mailslots using some browser-specific NETBIOS names. These datagrams can + * either be unicast or broadcast, depending on whether the NETBIOS name is + * a "unique name" or a "group name". Various data structures, which are + * detailed subsequently within this document, flow as the data portion of + * the Transact SMB. + * + * Here is an example of a generic browser SMB, showing how a browser + * request is encapsulated in a TRANSACT SMB request. Note that the PID, + * TID, MID, UID, and Flags are all 0 in mailslot requests. + * + * SMB: C transact, File = \MAILSLOT\BROWSE + * SMB: SMB Status = Error Success + * SMB: Error class = No Error + * SMB: Error code = No Error + * SMB: Header: PID = 0x0000 TID = 0x0000 MID = 0x0000 UID = 0x0000 + * SMB: Tree ID (TID) = 0 (0x0) + * SMB: Process ID (PID) = 0 (0x0) + * SMB: User ID (UID) = 0 (0x0) + * SMB: Multiplex ID (MID) = 0 (0x0) + * SMB: Flags Summary = 0 (0x0) + * SMB: Command = C transact + * SMB: Word count = 17 + * SMB: Word parameters + * SMB: Total parm bytes = 0 + * SMB: Total data bytes = 33 + * SMB: Max parm bytes = 0 + * SMB: Max data bytes = 0 + * SMB: Max setup words = 0 + * SMB: Transact Flags Summary = 0 (0x0) + * SMB: ...............0 = Leave session intact + * SMB: ..............0. = Response required + * SMB: Transact timeout = 0 (0x0) + * SMB: Parameter bytes = 0 (0x0) + * SMB: Parameter offset = 0 (0x0) + * SMB: Data bytes = 33 (0x21) + * SMB: Data offset = 86 (0x56) + * SMB: Setup word count = 3 + * SMB: Setup words + * SMB: Mailslot opcode = Write mailslot + * SMB: Transaction priority = 1 + * SMB: Mailslot class = Unreliable (broadcast) + * SMB: Byte count = 50 + * SMB: Byte parameters + * SMB: Path name = \MAILSLOT\BROWSE + * SMB: Transaction data + * SMB: Data: Number of data bytes remaining = 33 (0x0021) + * + * Note the SMB command is Transact, the opcode within the Transact SMB is + * Mailslot Write, and the browser data structure is carried as the + * Transact data. + * The Transaction data begins with an opcode, that signifies the operation + * and determines the size and structure of data that follows. This opcode + * is named as per one of the below: + * + * HostAnnouncement 1 + * AnnouncementRequest 2 + * RequestElection 8 + * GetBackupListReq 9 + * GetBackupListResp 10 + * BecomeBackup 11 + * DomainAnnouncment 12 + * MasterAnnouncement 13 + * LocalMasterAnnouncement 15 + * + * Browser datagrams are often referred to as simply browser frames. The + * frames are in particular, referred to by the name of the opcode within + * the Transaction data e.g. a GetBackupListReq browser frame, a + * RequestElection browser frame, etc. + * + * The structures that are sent as the data portion of the Transact SMB are + * described in section(s) 6.2 through 6.12 in this document. These + * structures are tightly packed, i.e. there are no intervening pad bytes + * in the structure, unless they are explicitly described as being there. + * All quantities are sent in native Intel format and multi-byte values are + * transmitted least significant byte first. + * + * Besides mailslots and Transaction SMBs, the other important piece of the + * browser architecture is the NetServerEnum2 request. This request that + * allows an application to interrogate a Browser Server and obtain a + * complete list of resources (servers, domains, etc) known to that Browser + * server. Details of the NetServerEnum2 request are presented in section + * 6.4. Some examples of the NetServerEnum2 request being used are when a + * Local Master Browser sends a NetServerEnum2 request to the Domain Master + * Browser and vice versa. Another example is when a browser client sends a + * NetServerEnum2 request to a Backup Browser server. + * + * 4.3 Non-Browser Server + * + * A non-browser server is a server that has some resource(s) or service(s) + * it wishes to advertise as being available using the browsing protocol. + * Examples of non-browser servers would be an SQL server, print server, + * etc. + * + * A non-browser server MUST periodically send a HostAnnouncement browser + * frame, specifying the type of resources or services it is advertising. + * Details are in section 6.5. + * + * A non-browser server SHOULD announce itself relatively frequently when + * it first starts up in order to make its presence quickly known to the + * browsers and thence to potential clients. The frequency of the + * announcements SHOULD then be gradually stretched, so as to minimize + * network traffic. Typically, non-browser servers announce themselves + * once every minute upon start up and then gradually adjust the frequency + * of the announcements to once every 12 minutes. + * + * A non-browser server SHOULD send a HostAnnouncement browser frame + * specifying a type of 0 just prior to shutting down, to allow it to + * quickly be removed from the list of available servers. + * + * A non-browser server MUST receive and process AnnouncementRequest frames + * from the Local Master Browser, and MUST respond with a HostAnnouncement + * frame, after a delay chosen randomly from the interval [0,30] seconds. + * AnnouncementRequests typically happen when a Local Master Browser starts + * up with an empty list of servers for the domain, and wants to fill it + * quickly. The 30 second range for responses prevents the Master Browser + * from becoming overloaded and losing replies, as well as preventing the + * network from being flooded with responses. + * + * 4.4 Browser Servers + * + * The following sections describe the roles of the various types of + * browser servers. + * + * 4.4.1 Potential Browser Server + * + * A Potential Browser server is a browser server that is capable of being + * a Backup Browser server or Master Browser server, but is not currently + * fulfilling either of those roles. + * + * A Potential Browser MUST set type SV_TYPE_POTENTIAL_BROWSER (see section + * 6.4.1) in its HostAnnouncement until it is ready to shut down. In its + * last HostAnnouncement frame before it shuts down, it SHOULD specify a + * type of 0. + * + * A Potential Browser server MUST receive and process BecomeBackup frames + * (see section 6.9) and become a backup browser upon their receipt. + * + * A Potential Browser MUST participate in browser elections (see section + * 6.8). + * + * 4.4.2 Backup Browser + * + * Backup Browser servers are a subset of the Potential Browsers that have + * been chosen by the Master Browser on their subnet to be the Backup + * Browsers for the subnet. + * + * A Backup Browser MUST set type SV_TYPE_BACKUP_BROWSER (see section + * 6.4.1) in its HostAnnouncement until it is ready to shut down. In its + * last HostAnnouncement frame before it shuts down, it SHOULD specify a + * type of 0. + * + * A Backup Browser MUST listen for a LocalMasterAnnouncement frame (see + * section 6.10) from the Local Master Browser, and use it to set the name + * of the Master Browser it queries for the server and domain lists. + * + * A Backup Browsers MUST periodically make a NetServerEnum2 request of + * the Master Browser on its subnet for its domain to get a list of servers + * in that domain, as well as a list of domains. The period is a + * configuration option balancing currency of the information with network + * traffic costs - a typical value is 15 minutes. + * + * A Backup Browser SHOULD force an election by sending a RequestElection + * frame (see section 6.7) if it does not get a response to its periodic + * NetServeEnum2 request to the Master Browser. + * + * A Backup Browser MUST receive and process NetServerEnum2 requests from + * browser clients, for its own domain and others. If the request is for a + * list of servers in its domain, or for a list of domains, it can answer + * from its internal lists. If the request is for a list of servers in a + * domain different than the one it serves, it sends a NetServerEnum2 + * request to the Domain Master Browser for that domain (which it can in + * find in its list of domains and their Domain Master Browsers). + * + * A Backup Browser MUST participate in browser elections (see section + * 6.8). + * + * 4.4.3 Master Browser + * + * Master Browsers are responsible for: + * . indicating it is a Master Browser + * . receiving server announcements and building a list of such servers + * and keeping it reasonably up-to-date. + * . returning lists of Backup Browsers to browser clients. + * . ensuring an appropriate number of Backup Browsers are available. + * . announcing their existence to other Master Browsers on their subnet, + * to the Domain Master Browser for their domain, and to all browsers in + * their domain on their subnet + * . forwarding requests for lists of servers on other domains to the + * Master Browser for that domain + * . keeping a list of domains in its subnet + * . synchronizing with the Domain Master Browser (if any) for its domain + * . participating in browser elections + * . ensuring that there is only one Master Browser on its subnet + * + * A Master Browser MUST set type SV_TYPE_MASTER_BROWSER (see section + * 6.4.1) in its HostAnnouncement until it is ready to shut down. In its + * last HostAnnouncement frame before it shuts down, it SHOULD specify a + * type of 0. + * + * A Master Browser MUST receive and process HostAnnouncement frames from + * servers, adding the server name and other information to its servers + * list; it must mark them as "local" entries. Periodically, it MUST check + * all local server entries to see if a server's HostAnnouncement has timed + * out (no HostAnnouncement received for three times the periodicity the + * server gave in the last received HostAnnouncement) and remove timed-out + * servers from its list. + * + * A Master Browser MUST receive and process DomainAnnouncement frames (see + * section 6.12) and maintain the domain names and their associated (Local) + * Master Browsers in its internal domain list until they time out; it must + * mark these as "local" entries. Periodically, it MUST check all local + * domain entries to see if a server's DomainAnnouncement has timed out (no + * DomainAnnouncement received for three times the periodicity the server + * gave in the last received DomainAnnouncement) and remove timed-out + * servers from its list. + * + * A Master Browser MUST receive and process GetBackupListRequest frames + * from clients, returning GetBackupListResponse frames containing a list + * of the Backup Servers for its domain. + * + * A Master Browser MUST eventually send BecomeBackup frames (see section + * 6.9) to one or more Potential Browser servers to increase the number of + * Backup Browsers if there are not enough Backup Browsers to handle the + * anticipated query load. Note: possible good times for checking for + * sufficient backup browsers are after being elected, when timing out + * server HostAnnouncements, and when receiving a server's HostAnnouncement + * for the first time. + * + * A Master Browser MUST periodically announce itself and the domain it + * serves to other (Local) Master Browsers on its subnet, by sending a + * DomainAnnouncement frame (see section 6.12) to its subnet. + * + * A Master Browser MUST send a MasterAnnouncement frame (see section 6.11) + * to the Domain Master Browser after it is first elected, and periodically + * thereafter. This informs the Domain Master Browser of the presence of + * all the Master Browsers. + * + * A Master Browser MUST periodically announce itself to all browsers for + * its domain on its subnet by sending a LocalMasterAnnouncement frame (see + * section 6.10). + * + * A Master Browser MUST receive and process NetServerEnum2 requests from + * browser clients, for its own domain and others. If the request is for a + * list of servers in its domain, or for a list of domains, it can answer + * from its internal lists. Entries in its list marked "local" MUST have + * the SV_TYPE_LOCAL_LIST_ONLY bit set in the returned results; it must be + * clear for all other entries. If the request is for a list of servers in + * a domain different than the one it serves, it sends a NetServerEnum2 + * request to the Domain Master Browser for that domain (which it can in + * find in its list of domains and their Domain Master Browsers). + * + * Note: The list of servers that the Master Browser maintains and + * returns to the Backup Browsers, is limited in size to 64K of + * data. This will limit the number of systems that can be in a + * browse list in a single workgroup or domain to approximately two + * thousand systems. + * + * A Master Browser SHOULD request all servers to register with it by + * sending an AnnouncementRequest frame, if, on becoming the Master Browser + * by winning an election, its server list is empty. Otherwise, clients + * might get an incomplete list of servers until the servers' periodic + * registrations fill the server list. + * + * If the Master Browser on a subnet is not the Primary Domain Controller + * (PDC), then it is a Local Master Browser. + * + * A Local Master Browser MUST periodically synchronize with the Domain + * Master Browser (which is the PDC). This synchronization is performed by + * making a NetServerEnum2 request to the Domain Master Browser and merging + * the results with its list of servers and domains. An entry from the + * Domain Master Browser should be marked "non-local", and must not + * overwrite an entry with the same name marked "local". The Domain Master + * Browser is located as specified in Appendix B. + * + * A Master Browser MUST participate in browser elections (see section + * 6.8). + * + * A Master Browser MUST, if it receives a HostAnnouncement, + * DomainAnnouncement, or LocalMasterAnnouncement frame another system that + * claims to be the Master Browser for its domain, demote itself from + * Master Browser and force an election. This ensures that there is only + * ever one Master Browser in each workgroup or domain. + * + * A Master Browser SHOULD, if it loses an election, become a Backup + * Browser (without being told to do so by the new Master Browser). Since + * it has more up-to-date information in its lists than a Potential + * Browser, it is more efficient to have it be a Backup Browser than to + * promote a Potential Browser. + * + * 4.4.3.1 Preferred Master Browser + * + * A Preferred Master Browser supports exactly the same protocol elements + * as a Potential Browser, except as follows. + * + * A Preferred Master Browser MUST always force an election when it starts + * up. + * + * A Preferred Master Browser MUST participate in browser elections (see + * section 6.8). + * + * A Preferred Master Browser MUST set the Preferred Master bit in the + * RequestElection frame (see section 6.7) to bias the election in its + * favor. + * + * A Preferred Master Browser SHOULD, if it loses an election, + * automatically become a Backup Browser, without being told to do so by + * the Master Browser. + * + * 4.4.4 Domain Master Browser + * + * Since the Domain Master Browser always runs on the PDC, it must + * implement all the protocols required of a PDC in addition to the + * browsing protocol, and that is way beyond the scope of this + * specification. + * + * 5. Mailslot Protocol Specification + * + * The only transaction allowed to a mailslot is a mailslot write. Mailslot + * writes requests are encapsulated in TRANSACT SMBs. The following table + * shows the interpretation of the TRANSACT SMB parameters for a mailslot + * transaction: + * + * Name Value Description + * Command SMB_COM_TRANSACTION + * Name <name> STRING name of mail slot to write; + * must start with "\\MAILSLOT\\" + * SetupCount 3 Always 3 for mailslot writes + * Setup[0] 1 Command code == write mailslot + * Setup[1] Ignored + * Setup[2] Ignored + * TotalDataCount n Size of data in bytes to write to + * the mailslot + * Data[ n ] The data to write to the mailslot + * + */ + +/* + * SMB: C transact, File = \MAILSLOT\BROWSE + * SMB: SMB Status = Error Success + * SMB: Error class = No Error + * SMB: Error code = No Error + * SMB: Header: PID = 0x0000 TID = 0x0000 MID = 0x0000 UID = 0x0000 + * SMB: Tree ID (TID) = 0 (0x0) + * SMB: Process ID (PID) = 0 (0x0) + * SMB: User ID (UID) = 0 (0x0) + * SMB: Multiplex ID (MID) = 0 (0x0) + * SMB: Flags Summary = 0 (0x0) + * SMB: Command = C transact + * SMB: Word count = 17 + * SMB: Word parameters + * SMB: Total parm bytes = 0 + * SMB: Total data bytes = 33 + * SMB: Max parm bytes = 0 + * SMB: Max data bytes = 0 + * SMB: Max setup words = 0 + * SMB: Transact Flags Summary = 0 (0x0) + * SMB: ...............0 = Leave session intact + * SMB: ..............0. = Response required + * SMB: Transact timeout = 0 (0x0) + * SMB: Parameter bytes = 0 (0x0) + * SMB: Parameter offset = 0 (0x0) + * SMB: Data bytes = 33 (0x21) + * SMB: Data offset = 86 (0x56) + * SMB: Setup word count = 3 + * SMB: Setup words + * SMB: Mailslot opcode = Write mailslot + * SMB: Transaction priority = 1 + * SMB: Mailslot class = Unreliable (broadcast) + * SMB: Byte count = 50 + * SMB: Byte parameters + * SMB: Path name = \MAILSLOT\BROWSE + * SMB: Transaction data + * SMB: Data: Number of data bytes remaining = 33 (0x0021) + * + * 5. Mailslot Protocol Specification + * + * The only transaction allowed to a mailslot is a mailslot write. Mailslot + * writes requests are encapsulated in TRANSACT SMBs. The following table + * shows the interpretation of the TRANSACT SMB parameters for a mailslot + * transaction: + * + * Name Value Description + * Command SMB_COM_TRANSACTION + * Name <name> STRING name of mail slot to write; + * must start with "\MAILSLOT\" + * SetupCount 3 Always 3 for mailslot writes + * Setup[0] 1 Command code == write mailslot + * Setup[1] Ignored + * Setup[2] Ignored + * TotalDataCount n Size of data in bytes to write to + * the mailslot + * Data[ n ] The data to write to the mailslot + * + * Magic 0xFF 'S' 'M' 'B' + * smb_com a byte, the "first" command + * Error a 4-byte union, ignored in a request + * smb_flg a one byte set of eight flags + * smb_flg2 a two byte set of 16 flags + * . twelve reserved bytes, have a role + * in connectionless transports (IPX, UDP?) + * smb_tid a 16-bit tree ID, a mount point sorta, + * 0xFFFF is this command does not have + * or require a tree context + * smb_pid a 16-bit process ID + * smb_uid a 16-bit user ID, specific to this "session" + * and mapped to a system (bona-fide) UID + * smb_mid a 16-bit multiplex ID, used to differentiate + * multiple simultaneous requests from the same + * process (pid) (ref RPC "xid") + */ + +int +smb_browser_load_transact_header(unsigned char *buffer, int maxcnt, + int data_count, int reply, char *mailbox) +{ + smb_msgbuf_t mb; + int mailboxlen; + char *fmt; + int result; + short class = (reply == ONE_WAY_TRANSACTION) ? 2 : 0; + + /* + * If the mailboxlen is an even number we need to pad the + * header so that the data starts on a word boundary. + */ + fmt = "Mb4.bw20.bwwwwb.wl2.wwwwb.wwwws"; + mailboxlen = strlen(mailbox) + 1; + + if ((mailboxlen & 0x01) == 0) { + ++mailboxlen; + fmt = "Mb4.bw20.bwwwwb.wl2.wwwwb.wwwws."; + } + + bzero(buffer, maxcnt); + smb_msgbuf_init(&mb, buffer, maxcnt, 0); + + result = smb_msgbuf_encode(&mb, fmt, + SMB_COM_TRANSACTION, /* Command */ + 0x18, + 0x3, + 17, /* Count of parameter words */ + 0, /* Total Parameter words sent */ + data_count, /* Total Data bytes sent */ + 2, /* Max Parameters to return */ + 0, /* Max data bytes to return */ + 0, /* Max setup bytes to return */ + reply, /* No reply */ + 0xffffffff, /* Timeout */ + 0, /* Parameter bytes sent */ + 0, /* Parameter offset */ + data_count, /* Data bytes sent */ + 69 + mailboxlen, /* Data offset */ + 3, /* Setup word count */ + 1, /* Setup word[0] */ + 0, /* Setup word[1] */ + class, /* Setup word[2] */ + mailboxlen + data_count, /* Total request bytes */ + mailbox); /* Mailbox address */ + + smb_msgbuf_term(&mb); + return (result); +} + +/* + * smb_net_id + * + * Lookup for the given IP in the NICs info table. + * If it finds a matching entry it'll return the index, + * otherwise returns -1. + * + * SMB network table and SMB browser info table share + * the same index. + */ +int +smb_net_id(uint32_t ipaddr) +{ + uint32_t myaddr, mask; + int net, smb_nc_cnt; + + smb_nc_cnt = smb_nic_get_num(); + for (net = 0; net < smb_nc_cnt; net++) { + net_cfg_t cfg; + if (smb_nic_get_byind(net, &cfg) == NULL) + break; + mask = cfg.mask; + myaddr = cfg.ip; + if ((ipaddr & mask) == (myaddr & mask)) + return (net); + } + + return (-1); +} + +/* + * smb_browser_get_srvname + * + */ +struct name_entry * +smb_browser_get_srvname(unsigned short netid) +{ + if (netid < smb_nic_get_num()) + return (&(smb_browser_info[netid].server.name)); + + return (NULL); +} + +static int +smb_browser_addr_of_subnet(struct name_entry *name, int subnet, + struct name_entry *result) +{ + uint32_t ipaddr, mask, saddr; + struct addr_entry *addr; + int smb_nc_cnt; + net_cfg_t cfg; + + smb_nc_cnt = smb_nic_get_num(); + if ((name == 0) || subnet >= smb_nc_cnt) + return (-1); + + if (smb_nic_get_byind(subnet, &cfg) == NULL) + return (-1); + ipaddr = cfg.ip; + mask = cfg.mask; + + *result = *name; + addr = &name->addr_list; + do { + saddr = addr->sin.sin_addr.s_addr; + if ((saddr & mask) == (ipaddr & mask)) { + *result = *name; + result->addr_list = *addr; + result->addr_list.forw = result->addr_list.back = + &result->addr_list; + return (0); + } + addr = addr->forw; + } while (addr != &name->addr_list); + + return (-1); +} + + +static int +smb_browser_bcast_addr_of_subnet(struct name_entry *name, int net, + struct name_entry *result) +{ + uint32_t broadcast; + int smb_nc_cnt; + net_cfg_t cfg; + + smb_nc_cnt = smb_nic_get_num(); + if (net >= smb_nc_cnt) + return (-1); + + if (name != 0 && name != result) + *result = *name; + + if (smb_nic_get_byind(net, &cfg) == NULL) + return (-1); + + broadcast = cfg.broadcast; + result->addr_list.sin.sin_family = AF_INET; + result->addr_list.sinlen = sizeof (result->addr_list.sin); + result->addr_list.sin.sin_addr.s_addr = broadcast; + result->addr_list.sin.sin_port = htons(DGM_SRVC_UDP_PORT); + result->addr_list.forw = result->addr_list.back = &result->addr_list; + return (0); +} + +/* + * 6.5 HostAnnouncement Browser Frame + * + * To advertise its presence, i.e. to publish itself as being available, a + * non-browser server sends a HostAnnouncement browser frame. If the server + * is a member of domain "D", this frame is sent to the NETBIOS unique name + * D(1d) and mailslot "\\MAILSLOT\\BROWSE". The definition of the + * HostAnnouncement frame is: + * + * struct { + * unsigned short Opcode; + * unsigned char UpdateCount; + * uint32_t Periodicity; + * unsigned char ServerName[]; + * unsigned char VersionMajor; + * unsigned char VersionMinor; + * uint32_t Type; + * uint32_t Signature; + * unsigned char Comment[]; + * } + * + * where: + * Opcode - Identifies this structure as a browser server + * announcement and is defined as HostAnnouncement with a + * value of decimal 1. + * + * UpdateCount - must be sent as zero and ignored on receipt. + * + * Periodicity - The announcement frequency of the server (in + * seconds). The server will be removed from the browse list + * if it has not been heard from in 3X its announcement + * frequency. In no case will the server be removed from the + * browse list before the period 3X has elapsed. Actual + * implementations may take more than 3X to actually remove + * the server from the browse list. + * + * ServerName - Null terminated ASCII server name (up to 16 bytes + * in length). + * + * VersionMajor - The major version number of the OS the server + * is running. it will be returned by NetServerEnum2. + * + * VersionMinor - The minor version number of the OS the server + * is running. This is entirely informational and does not + * have any significance for the browsing protocol. + * + * Type - Specifies the type of the server. The server type bits + * are specified in the NetServerEnum2 section. + * + * Signature - The browser protocol minor version number in the + * low 8 bits, the browser protocol major version number in + * the next higher 8 bits and the signature 0xaa55 in the + * high 16 bits of this field. Thus, for this version of the + * browser protocol (1.15) this field has the value + * 0xaa55010f. This may used to isolate browser servers that + * are running out of revision browser software; otherwise, + * it is ignored. + * + * Comment - Null terminated ASCII comment for the server. + * Limited to 43 bytes. + * + * When a non-browser server starts up, it announces itself in the manner + * described once every minute. The frequency of these statements is + * gradually stretched to once every 12 minutes. + * + * Note: older non-browser servers in a domain "D" sent HostAnnouncement + * frames to the NETBIOS group name D(00). Non-Browser servers supporting + * version 1.15 of the browsing protocol SHOULD NOT use this NETBIOS name, + * but for backwards compatibility Master Browsers MAY receive and process + * HostAnnouncement frames on this name as described above for D(1d). + */ + +void +smb_browser_send_HostAnnouncement(int net, int32_t next_announcement, + struct addr_entry *addr, char suffix) +{ + smb_msgbuf_t mb; + int offset, announce_len, data_length; + struct name_entry dest_name; + struct name_entry server_name; + struct browser_netinfo *subnet; + server_info_t *server; + unsigned char *buffer; + uint32_t type; + char resource_domain[SMB_PI_MAX_DOMAIN]; + + syslog(LOG_DEBUG, "smb_browse: send_HostAnnouncement(%d)", net); + + smb_config_rdlock(); + (void) strlcpy(resource_domain, + smb_config_getstr(SMB_CI_DOMAIN_NAME), SMB_PI_MAX_DOMAIN); + (void) utf8_strupr(resource_domain); + smb_config_unlock(); + + if (addr == 0) { + /* Local master Browser */ + smb_init_name_struct( + (unsigned char *)resource_domain, suffix, + 0, 0, 0, 0, 0, &dest_name); + if (smb_browser_bcast_addr_of_subnet(0, net, &dest_name) < 0) + return; + } else { + smb_init_name_struct( + (unsigned char *)resource_domain, suffix, + 0, 0, 0, 0, 0, &dest_name); + dest_name.addr_list = *addr; + dest_name.addr_list.forw = dest_name.addr_list.back = + &dest_name.addr_list; + } + + /* give some extra room */ + buffer = (unsigned char *)malloc(MAX_DATAGRAM_LENGTH * 2); + if (buffer == 0) { + syslog(LOG_ERR, "HostAnnouncement: resource shortage"); + return; + } + + subnet = smb_browser_getnet(net); + if (subnet == 0) { + free(buffer); + return; + } + + server = &subnet->server; + + data_length = 1 + 1 + 4 + 16 + 1 + 1 + 4 + 4 + + strlen(server->comment) + 1; + + if ((offset = smb_browser_load_transact_header(buffer, + MAX_DATAGRAM_LENGTH, data_length, ONE_WAY_TRANSACTION, + browse)) < 0) { + + smb_browser_putnet(subnet); + free(buffer); + return; + } + + /* + * A non-browser server SHOULD send a HostAnnouncement browser frame + * specifying a type of 0 just prior to shutting down, to allow it to + * quickly be removed from the list of available servers. + */ + type = (nb_status.state & NETBIOS_SHUTTING_DOWN) ? 0 : server->type; + + smb_msgbuf_init(&mb, buffer + offset, MAX_DATAGRAM_LENGTH - offset, 0); + announce_len = smb_msgbuf_encode(&mb, "bbl16cbblls", + (char)HOST_ANNOUNCEMENT, /* Announcement opcode */ + (char)++subnet->server.update_count, + next_announcement * 60000, /* Periodicity in MilliSeconds */ + server->hostname, /* Server name */ + server->major, /* our major version */ + server->minor, /* our minor version */ + type, /* server type */ + server->signature, /* Signature */ + server->comment); /* Let 'em know */ + + server_name = server->name; + smb_browser_putnet(subnet); + + if (announce_len > 0) + (void) smb_netbios_datagram_send(&server_name, &dest_name, + buffer, offset + announce_len); + + free(buffer); + smb_msgbuf_term(&mb); +} + +void +smb_browser_process_AnnouncementRequest(struct datagram *datagram, + char *mailbox) +{ + struct browser_netinfo *subnet; + unsigned int next_announcement; + uint32_t delay = random() % 29; /* in seconds */ + int net; + + if (strcmp(mailbox, lanman) != 0) { + syslog(LOG_DEBUG, "smb_browse: Wrong Mailbox (%s)", mailbox); + return; + } + + net = smb_net_id(datagram->src.addr_list.sin.sin_addr.s_addr); + if (net < 0) { + /* We don't know who this is so ignore it... */ + return; + } + + (void) sleep(delay); + + subnet = smb_browser_getnet(net); + if (subnet) { + next_announcement = subnet->next_announce * 60 * 1000; + smb_browser_putnet(subnet); + smb_browser_send_HostAnnouncement(net, next_announcement, + &datagram->src.addr_list, 0x1D); + } +} + +void * +smb_browser_dispatch(void *arg) +{ + struct datagram *datagram = (struct datagram *)arg; + smb_msgbuf_t mb; + int rc; + unsigned char command; + unsigned char parameter_words; + unsigned short total_parameter_words; + unsigned short total_data_count; + unsigned short max_parameters_to_return; + unsigned short max_data_to_return; + unsigned char max_setup_bytes_to_return; + unsigned short reply; + unsigned short parameter_bytes_sent; + unsigned short parameter_offset; + unsigned short data_bytes_sent; + unsigned short data_offset; + unsigned char setup_word_count; + unsigned short setup_word_0; + unsigned short setup_word_1; + unsigned short setup_word_2; + unsigned short total_request_bytes; + char *mailbox; + unsigned char message_type; + unsigned char *data; + int datalen; + + syslog(LOG_DEBUG, "smb_browse: packet_received"); + + smb_msgbuf_init(&mb, datagram->data, datagram->data_length, 0); + rc = smb_msgbuf_decode(&mb, "Mb27.bwwwwb.w6.wwwwb.wwwws", + &command, /* Command */ + ¶meter_words, /* Count of parameter words */ + &total_parameter_words, /* Total Parameter words sent */ + &total_data_count, /* Total Data bytes sent */ + &max_parameters_to_return, /* Max Parameters to return */ + &max_data_to_return, /* Max data bytes to return */ + &max_setup_bytes_to_return, /* Max setup bytes to return */ + &reply, /* No reply */ + ¶meter_bytes_sent, /* Parameter bytes sent */ + ¶meter_offset, /* Parameter offset */ + &data_bytes_sent, /* Data bytes sent */ + &data_offset, /* Data offset */ + &setup_word_count, /* Setup word count */ + &setup_word_0, /* Setup word[0] */ + &setup_word_1, /* Setup word[1] */ + &setup_word_2, /* Setup word[2] */ + &total_request_bytes, /* Total request bytes */ + &mailbox); /* Mailbox address */ + + if (rc < 0) { + syslog(LOG_ERR, "smb_browser_dispatch: decode error"); + smb_msgbuf_term(&mb); + free(datagram); + return (0); + } + + data = &datagram->data[data_offset]; + datalen = datagram->data_length - data_offset; + + /* + * The PDC location protocol, i.e. anything on the \\NET + * mailslot, is handled by the smb_netlogon module. + */ + if (strncasecmp("\\MAILSLOT\\NET\\", mailbox, 14) == 0) { + smb_netlogon_receive(datagram, mailbox, data, datalen); + smb_msgbuf_term(&mb); + free(datagram); + return (0); + } + + /* + * If it's not a netlogon message, assume it's a browser request. + * This is not the most elegant way to extract the command byte + * but at least we no longer use it to get the netlogon opcode. + */ + message_type = datagram->data[data_offset]; + + switch (message_type) { + case ANNOUNCEMENT_REQUEST : + smb_browser_process_AnnouncementRequest(datagram, mailbox); + break; + + default: + syslog(LOG_DEBUG, "smb_browse: invalid message_type(%d, %x)", + message_type, message_type); + break; + } + + smb_msgbuf_term(&mb); + free(datagram); + return (0); +} + + +/* + * 11.1 Registered unique names + * + * <COMPUTER>(00) + * This name is used by all servers and clients to receive second + * class mailslot messages. A system must add this name in order to + * receive mailslot messages. The only browser requests that should + * appear on this name are BecomeBackup, GetBackupListResp, + * MasterAnnouncement, and LocalMasterAnnouncement frames. All other + * datagrams (other than the expected non-browser datagrams) may be + * ignored and an error logged. + * + * <DOMAIN>(1d) + * This name is used to identify a master browser server for domain + * "DOMAIN" on a subnet. A master browser server adds this name as a + * unique NETBIOS name when it becomes master browser. If the attempt + * to add the name fails, the master browser server assumes that there + * is another master in the domain and will fail to come up. It may + * log an error if the failure occurs more than 3 times in a row (this + * either indicates some form of network misconfiguration or a + * software error). The only requests that should appear on this name + * are GetBackupListRequest and HostAnnouncement requests. All other + * datagrams on this name may be ignored (and an error logged). If + * running a NETBIOS name service (NBNS, such as WINS), this name + * should not be registered with the NBNS. + * + * <DOMAIN>(1b) + * This name is used to identify the Domain Master Browser for domain + * "DOMAIN" (which is also the primary domain controller). It is a + * unique name added only by the primary domain controller. The + * primary domain controller will respond to GetBackupListRequest on + * this name just as it responds to these requests on the <DOMAIN>(1d) + * name. + * + * 11.2 Registered group names + * + * (01)(02)__MSBROWSE__(02)(01) + * This name is used by Master Browsers to announce themselves to the + * other Master Browsers on a subnet. It is added as a group name by + * all Master Browser servers. The only broadcasts that should appear + * on this name is DomainAnnouncement requests. All other datagrams + * can be ignored. + * + * <DOMAIN>(00) + * This name is used by clients and servers in domain "DOMAIN" to + * process server announcements. The only requests that should appear + * on this name that the browser is interested in are + * AnnouncementRequest and NETLOGON_QUERY (to locate the PDC) packets. + * All other unidentifiable requests may be ignored (and an error + * logged). + * + * <DOMAIN>(1E) + * This name is used for announcements to browsers for domain "DOMAIN" + * on a subnet. This name is registered by all the browser servers in + * the domain. The only requests that should appear on this name are + * RequestElection and AnnouncementRequest packets. All other + * datagrams may be ignored (and an error logged). + * + * <DOMAIN>(1C) + * This name is registered by Primary Domain Controllers. + */ + +void +smb_browser_config(void) +{ + struct name_entry name; + struct name_entry master; + struct name_entry dest; + struct name_entry *entry; + int smb_nc_cnt; + net_cfg_t cfg; + int net; + char resource_domain[SMB_PI_MAX_DOMAIN]; + + syslog(LOG_DEBUG, "smb_browse: reconfigure"); + + smb_browser_init(); + + smb_config_rdlock(); + (void) strlcpy(resource_domain, + smb_config_getstr(SMB_CI_DOMAIN_NAME), SMB_PI_MAX_DOMAIN); + (void) utf8_strupr(resource_domain); + smb_config_unlock(); + + /* domain<00> */ + smb_init_name_struct((unsigned char *)resource_domain, 0x00, + 0, 0, 0, 0, 0, &name); + entry = smb_name_find_name(&name); + smb_name_unlock_name(entry); + + smb_nc_cnt = smb_nic_get_num(); + for (net = 0; net < smb_nc_cnt; net++) { + if (smb_nic_get_byind(net, &cfg) == NULL) + break; + if (cfg.exclude) + continue; + smb_init_name_struct( + (unsigned char *)resource_domain, 0x00, 0, + cfg.ip, htons(DGM_SRVC_UDP_PORT), + NAME_ATTR_GROUP, NAME_ATTR_LOCAL, &name); + (void) smb_name_add_name(&name); + } + + /* All our local master browsers */ + smb_init_name_struct((unsigned char *)resource_domain, 0x1D, + 0, 0, 0, 0, 0, &dest); + entry = smb_name_find_name(&dest); + + if (entry) { + for (net = 0; net < smb_nc_cnt; net++) { + if (smb_browser_addr_of_subnet(entry, net, &master) + == 0) { + syslog(LOG_DEBUG, + "smbd: Master browser found at %s", + inet_ntoa(master.addr_list.sin.sin_addr)); + } + } + smb_name_unlock_name(entry); + } + smb_init_name_struct((unsigned char *)resource_domain, + 0x1B, 0, 0, 0, 0, 0, &dest); + + if ((entry = smb_name_find_name(&dest)) != 0) { + syslog(LOG_DEBUG, "smbd: Domain Master browser for %s is %s", + resource_domain, + inet_ntoa(entry->addr_list.sin.sin_addr)); + smb_name_unlock_name(entry); + } +} + +static void +smb_browser_init() +{ + struct browser_netinfo *subnet; + struct server_info *server; + char cmnt[SMB_PI_MAX_COMMENT], hostname[MAXHOSTNAMELEN]; + int i, j; + int smb_nc_cnt; + net_cfg_t cfg; + + (void) smb_gethostname(hostname, MAXHOSTNAMELEN, 1); + + smb_config_rdlock(); + (void) strlcpy(cmnt, smb_config_getstr(SMB_CI_SYS_CMNT), + sizeof (cmnt)); + smb_config_unlock(); + + smb_nc_cnt = smb_nic_get_num(); + for (i = 0; i < smb_nc_cnt; i++) { + if (smb_nic_get_byind(i, &cfg) == NULL) + break; + if (cfg.exclude) + continue; + + subnet = &smb_browser_info[i]; + (void) mutex_lock(&subnet->mtx); + + /* One Minute announcements for first five */ + subnet->flags = BROWSER_NF_VALID; + subnet->next_announce = 1; + subnet->interval = 1; + subnet->reps = 5; + + server = &subnet->server; + bzero(server, sizeof (struct server_info)); + + server->type = MY_SERVER_TYPE; + server->major = SMB_VERSION_MAJOR; + server->minor = SMB_VERSION_MINOR; + server->signature = SMB_SERVER_SIGNATURE; + (void) strlcpy(server->comment, cmnt, SMB_PI_MAX_COMMENT); + + (void) snprintf(server->hostname, NETBIOS_NAME_SZ, "%.15s", + hostname); + + /* + * 00 is workstation service. + * 20 is file server service. + */ + smb_init_name_struct((unsigned char *)server->hostname, 0x20, 0, + cfg.ip, htons(DGM_SRVC_UDP_PORT), + NAME_ATTR_UNIQUE, NAME_ATTR_LOCAL, &server->name); + + (void) mutex_unlock(&subnet->mtx); + } + + /* Invalidate unconfigured NICs */ + for (j = i; j < SMB_PI_MAX_NETWORKS; j++) { + subnet = &smb_browser_info[j]; + (void) mutex_lock(&subnet->mtx); + subnet->flags = BROWSER_NF_INVALID; + (void) mutex_unlock(&subnet->mtx); + } +} + +/* + * smb_browser_non_master_duties + * + * To advertise its presence, i.e. to publish itself as being available, a + * non-browser server sends a HostAnnouncement browser frame. If the server + * is a member of domain "D", this frame is sent to the NETBIOS unique name + * D(1d) and mailslot "\\MAILSLOT\\BROWSE". + */ +void +smb_browser_non_master_duties(int net) +{ + struct browser_netinfo *subnet; + struct name_entry name; + struct name_entry *dest; + struct addr_entry addr; + int interval; + char resource_domain[SMB_PI_MAX_DOMAIN]; + + subnet = smb_browser_getnet(net); + if (subnet == 0) + return; + + interval = subnet->interval; + smb_browser_putnet(subnet); + + smb_browser_send_HostAnnouncement(net, interval, 0, 0x1D); + + smb_config_rdlock(); + (void) strlcpy(resource_domain, + smb_config_getstr(SMB_CI_DOMAIN_NAME), SMB_PI_MAX_DOMAIN); + (void) utf8_strupr(resource_domain); + smb_config_unlock(); + + smb_init_name_struct((unsigned char *)resource_domain, 0x1D, + 0, 0, 0, 0, 0, &name); + + if ((dest = smb_name_find_name(&name))) { + addr = dest->addr_list; + addr.forw = addr.back = &addr; + smb_name_unlock_name(dest); + smb_browser_send_HostAnnouncement(net, interval, &addr, 0x1D); + } else { + smb_init_name_struct( + (unsigned char *)resource_domain, 0x1B, + 0, 0, 0, 0, 0, &name); + if ((dest = smb_name_find_name(&name))) { + addr = dest->addr_list; + addr.forw = addr.back = &addr; + smb_name_unlock_name(dest); + smb_browser_send_HostAnnouncement(net, interval, + &addr, 0x1B); + } + } + + subnet = smb_browser_getnet(net); + /* + * One Minute announcements for first five + * minutes, one munute longer each round + * until 12 minutes and every 12 minutes + * thereafter. + */ + if (--subnet->reps == 0) { + if (subnet->interval < 12) + subnet->interval++; + + subnet->reps = 1; + } + + subnet->next_announce = subnet->interval; + smb_browser_putnet(subnet); +} + + +/* + * browser_sleep + * + * Put browser in 1 minute sleep if netbios services are not + * shutting down and both name and datagram services are still + * running. It'll wake up after 1 minute or if one of the above + * conditions go false. It checks the conditions again and return + * 1 if everything is ok or 0 if browser shouldn't continue + * running. + */ +static int +browser_sleep() +{ + int slept = 0; + timestruc_t to; + + (void) mutex_lock(&nb_status.mtx); + while (((nb_status.state & NETBIOS_SHUTTING_DOWN) == 0) && + (nb_status.state & NETBIOS_NAME_SVC_RUNNING) && + (nb_status.state & NETBIOS_DATAGRAM_SVC_RUNNING)) { + + if (slept) { + (void) mutex_unlock(&nb_status.mtx); + return (1); + } + + to.tv_sec = 60; /* 1 minute */ + to.tv_nsec = 0; + (void) cond_reltimedwait(&nb_status.cv, &nb_status.mtx, &to); + slept = 1; + } + (void) mutex_unlock(&nb_status.mtx); + + return (0); +} + +/* + * smb_browser_start + * + * Smb Netbios browser daemon. + */ +/*ARGSUSED*/ +void * +smb_browser_daemon(void *arg) +{ + int net; + int next_announce; + struct browser_netinfo *subnet; + int run = 1; + int smb_nc_cnt; + net_cfg_t cfg; + + smb_browser_config(); + + nb_status.state |= NETBIOS_BROWSER_RUNNING; + + while (run) { + smb_nc_cnt = smb_nic_get_num(); + for (net = 0; net < smb_nc_cnt; net++) { + if (smb_nic_get_byind(net, &cfg) == NULL) + break; + if (cfg.exclude) + continue; + + subnet = smb_browser_getnet(net); + next_announce = --subnet->next_announce; + smb_browser_putnet(subnet); + + if (next_announce > 0 || cfg.broadcast == 0) + continue; + + smb_browser_non_master_duties(net); + } + + run = browser_sleep(); + } + + smb_netbios_chg_status(NETBIOS_BROWSER_RUNNING, 0); + return (0); +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_browser.h b/usr/src/lib/smbsrv/libsmbns/common/smbns_browser.h new file mode 100644 index 0000000000..17f3fdf4a9 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_browser.h @@ -0,0 +1,190 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _BROWSER_H_ +#define _BROWSER_H_ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * NetBIOS name types describe the functionality of the registration. + * A following list of NetBIOS suffixes (16th Character of the NetBIOS + * Name) is detailed in Microsoft knowledge base article Q163409. + * + * Name Number(h) Type Usage + * -------------------------------------------------------------------------- + * <computername> 00 U Workstation Service + * <computername> 01 U Messenger Service + * <\\--__MSBROWSE__> 01 G Master Browser + * <computername> 03 U Messenger Service + * <computername> 06 U RAS Server Service + * <computername> 1F U NetDDE Service + * <computername> 20 U File Server Service + * <computername> 21 U RAS Client Service + * <computername> 22 U Microsoft Exchange Interchange(MSMail + * Connector) + * <computername> 23 U Microsoft Exchange Store + * <computername> 24 U Microsoft Exchange Directory + * <computername> 30 U Modem Sharing Server Service + * <computername> 31 U Modem Sharing Client Service + * <computername> 43 U SMS Clients Remote Control + * <computername> 44 U SMS Administrators Remote Control + * Tool + * <computername> 45 U SMS Clients Remote Chat + * <computername> 46 U SMS Clients Remote Transfer + * <computername> 4C U DEC Pathworks TCPIP service on + * Windows NT + * <computername> 52 U DEC Pathworks TCPIP service on + * Windows NT + * <computername> 87 U Microsoft Exchange Message Transfer + * Agent + * <computername> 6A U Microsoft Exchange IMC + * <computername> BE U Network Monitor Agent + * <computername> BF U Network Monitor Application + * <username> 03 U Messenger Service + * <domain> 00 G Domain Name + * <domain> 1B U Domain Master Browser + * <domain> 1C G Domain Controllers + * <domain> 1D U Master Browser + * <domain> 1E G Browser Service Elections + * <INet~Services> 1C G IIS + * <IS~computer name> 00 U IIS + * <computername> [2B] U Lotus Notes Server Service + * IRISMULTICAST [2F] G Lotus Notes + * IRISNAMESERVER [33] G Lotus Notes + * Forte_$ND800ZA [20] U DCA IrmaLan Gateway Server Service + * + * Unique (U): The name may have only one IP address assigned to it. On + * a network device multiple occurrences of a single name may appear to + * be registered. The suffix may be the only unique character in the name. + * + * Group (G): A normal group; the single name may exist with many IP + * addresses. WINS responds to a name query on a group name with the + * limited broadcast address (255.255.255.255). Because routers block + * the transmission of these addresses, the Internet Group was designed + * to service communications between subnets. + * + * Multihomed (M): The name is unique, but due to multiple network + * interfaces on the same computer this configuration is necessary to + * permit the registration. The maximum number of addresses is 25. + * + * Internet Group (I): This is a special configuration of the group name + * used to manage Windows NT Domain names. + * + * Domain Name (D): New in Windows NT 4.0. + */ + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * Message flags used when building the SMB transact headers. + */ +#define TWO_WAY_TRANSACTION 0x00 +#define END_SESSION_TRANSACTION 0x01 +#define ONE_WAY_TRANSACTION 0x02 + + +/* + * Browser commands associated with the BROWSE and MSBROWSE mailslots. + */ +#define HOST_ANNOUNCEMENT 0x01 +#define ANNOUNCEMENT_REQUEST 0x02 +#define REQUEST_ELECTION 0x08 +#define GET_BACKUP_LIST_REQ 0x09 +#define GET_BACKUP_LIST_RESP 0x0A +#define BECOME_BACKUP 0x0B +#define DOMAIN_ANNOUNCEMENT 0x0C +#define MASTER_ANNOUNCEMENT 0x0D +#define LOCAL_MASTER_ANNOUNCEMENT 0x0F + + +/* + * Opcodes associated with NETLOGON or NTLOGON mailslots (KB 109626). + * LOGON_REQUEST LM1.0/2.0 LOGON Request from client + * LOGON_RESPONSE LM1.0 Response to LOGON_REQUEST + * LOGON_CENTRAL_QUERY LM1.0 QUERY for centralized init + * LOGON_DISTRIB_QUERY LM1.0 QUERY for non-centralized init + * LOGON_CENTRAL_RESPONSE LM1.0 response to LOGON_CENTRAL_QUERY + * LOGON_DISTRIB_RESPONSE LM1.0 resp to LOGON_DISTRIB_QUERY + * LOGON_RESPONSE2 LM2.0 Response to LOGON_REQUEST + * LOGON_PRIMARY_QUERY QUERY for Primary DC + * LOGON_START_PRIMARY announce startup of Primary DC + * LOGON_FAIL_PRIMARY announce failed Primary DC + * LOGON_UAS_CHANGE announce change to UAS or SAM + * LOGON_NO_USER announce no user on machine + * LOGON_PRIMARY_RESPONSE response to LOGON_PRIMARY_QUERY + * LOGON_RELOGON_RESPONSE LM1.0/2.0 resp to relogon request + * LOGON_WKSTINFO_RESPONSE LM1.0/2.0 resp to interrogate request + * LOGON_PAUSE_RESPONSE LM2.0 resp when NETLOGON is paused + * LOGON_USER_UNKNOWN LM2.0 response when user is unknown + * LOGON_UPDATE_ACCOUNT LM2.1 announce account updates + * LOGON_SAM_LOGON_REQUEST SAM LOGON request from client + * LOGON_SAM_LOGON_RESPONSE SAM Response to SAM logon request + * LOGON_SAM_PAUSE_RESPONSE SAM response when NETLOGON is paused + * LOGON_SAM_USER_UNKNOWN SAM response when user is unknown + * LOGON_SAM_WKSTINFO_RESPONSE SAM response to interrogate request + */ +#define LOGON_REQUEST 0 +#define LOGON_RESPONSE 1 +#define LOGON_CENTRAL_QUERY 2 +#define LOGON_DISTRIB_QUERY 3 +#define LOGON_CENTRAL_RESPONSE 4 +#define LOGON_DISTRIB_RESPONSE 5 +#define LOGON_RESPONSE2 6 +#define LOGON_PRIMARY_QUERY 7 +#define LOGON_START_PRIMARY 8 +#define LOGON_FAIL_PRIMARY 9 +#define LOGON_UAS_CHANGE 10 +#define LOGON_NO_USER 11 +#define LOGON_PRIMARY_RESPONSE 12 +#define LOGON_RELOGON_RESPONSE 13 +#define LOGON_WKSTINFO_RESPONSE 14 +#define LOGON_PAUSE_RESPONSE 15 +#define LOGON_USER_UNKNOWN 16 +#define LOGON_UPDATE_ACCOUNT 17 +#define LOGON_SAM_LOGON_REQUEST 18 +#define LOGON_SAM_LOGON_RESPONSE 19 +#define LOGON_SAM_PAUSE_RESPONSE 20 +#define LOGON_SAM_USER_UNKNOWN 21 +#define LOGON_SAM_WKSTINFO_RESPONSE 22 + + +/* + * Local protocol flags used to indicate which version of the + * netlogon protocol to use when attempting to find the PDC. + */ +#define NETLOGON_PROTO_NETLOGON 0x01 +#define NETLOGON_PROTO_SAMLOGON 0x02 + +#ifdef __cplusplus +} +#endif + + +#endif /* _BROWSER_H_ */ diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c new file mode 100644 index 0000000000..474adca09b --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c @@ -0,0 +1,1894 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/time.h> +#include <unistd.h> +#include <string.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include <netdb.h> +#include <rpc/rpc.h> +#include <syslog.h> +#include <gssapi/gssapi.h> +#include <kerberosv5/krb5.h> +#include <net/if.h> + +#include <smbns_dyndns.h> +#include <smbns_krb.h> + +/* internal use, in dyndns_add_entry */ +#define DEL_NONE 2 +/* Maximum retires if not authoritative */ +#define MAX_AUTH_RETRIES 3 + + +static int +dyndns_enabled(void) +{ + int enabled; + + smb_config_rdlock(); + enabled = smb_config_getyorn(SMB_CI_DYNDNS_ENABLE); + smb_config_unlock(); + + return ((enabled) ? 1 : 0); +} + +/* + * XXX The following should be removed once head/arpa/nameser_compat.h + * defines BADSIG, BADKEY, BADTIME macros + */ +#ifndef BADSIG +#define BADSIG ns_r_badsig +#endif /* BADSIG */ + +#ifndef BADKEY +#define BADKEY ns_r_badkey +#endif /* BADKEY */ + +#ifndef BADTIME +#define BADTIME ns_r_badtime +#endif /* BADTIME */ + +/* + * dyndns_msg_err + * Display error message for DNS error code found in the DNS header in + * reply message. + * Parameters: + * err: DNS errer code + * Returns: + * None + */ +void +dyndns_msg_err(int err) +{ + switch (err) { + case NOERROR: + break; + case FORMERR: + syslog(LOG_ERR, "DNS message format error\n"); + break; + case SERVFAIL: + syslog(LOG_ERR, "DNS server internal error\n"); + break; + case NXDOMAIN: + syslog(LOG_ERR, "DNS entry should exist but does not exist\n"); + break; + case NOTIMP: + syslog(LOG_ERR, "DNS opcode not supported\n"); + break; + case REFUSED: + syslog(LOG_ERR, "DNS operation refused\n"); + break; + case YXDOMAIN: + syslog(LOG_ERR, "DNS entry shouldn't exist but does exist\n"); + break; + case YXRRSET: + syslog(LOG_ERR, "DNS RRSet shouldn't exist but does exist\n"); + break; + case NXRRSET: + syslog(LOG_ERR, "DNS RRSet should exist but does not exist\n"); + break; + case NOTAUTH: + syslog(LOG_ERR, "DNS server is not authoritative " + "for specified zone\n"); + break; + case NOTZONE: + syslog(LOG_ERR, "Name in Prereq or Update section not " + "within specified zone\n"); + break; + case BADSIG: + syslog(LOG_ERR, "Bad transaction signature (TSIG)"); + break; + case BADKEY: + syslog(LOG_ERR, "Bad transaction key (TKEY)"); + break; + case BADTIME: + syslog(LOG_ERR, "Time not synchronized"); + break; + + default: + syslog(LOG_ERR, "Unknown DNS error\n"); + } +} + +/* + * display_stat + * Display GSS error message from error code. This routine is used to display + * the mechanism independent and mechanism specific error messages for GSS + * routines. The major status error code is the mechanism independent error + * code and the minor status error code is the mechanism specific error code. + * Parameters: + * maj: GSS major status + * min: GSS minor status + * Returns: + * None + */ +static void +display_stat(OM_uint32 maj, OM_uint32 min) +{ + gss_buffer_desc msg; + OM_uint32 msg_ctx = 0; + OM_uint32 min2; + (void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, + &msg_ctx, &msg); + syslog(LOG_ERR, "dyndns: GSS major status error: %s\n", + (char *)msg.value); + (void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, + &msg_ctx, &msg); + syslog(LOG_ERR, "dyndns: GSS minor status error: %s\n", + (char *)msg.value); +} + +static char * +dyndns_put_nshort(char *buf, uint16_t val) +{ + uint16_t nval; + + nval = htons(val); + (void) memcpy(buf, &nval, sizeof (uint16_t)); + buf += sizeof (uint16_t); + return (buf); +} + +char * +dyndns_get_nshort(char *buf, uint16_t *val) +{ + uint16_t nval; + + (void) memcpy(&nval, buf, sizeof (uint16_t)); + *val = ntohs(nval); + buf += sizeof (uint16_t); + return (buf); +} + +static char * +dyndns_put_nlong(char *buf, uint32_t val) +{ + uint32_t lval; + + lval = htonl(val); + (void) memcpy(buf, &lval, sizeof (uint32_t)); + buf += sizeof (uint32_t); + return (buf); +} + +static char * +dyndns_put_byte(char *buf, char val) +{ + *buf = val; + buf++; + return (buf); +} + +static char * +dyndns_put_int(char *buf, int val) +{ + (void) memcpy(buf, &val, sizeof (int)); + buf += sizeof (int); + return (buf); +} + +char * +dyndns_get_int(char *buf, int *val) +{ + (void) memcpy(val, buf, sizeof (int)); + buf += sizeof (int); + return (buf); +} + + +/* + * dyndns_stuff_str + * Converts a domain string by removing periods and replacing with a byte value + * of how many characters following period. A byte value is placed in front + * to indicate how many characters before first period. A NULL character is + * placed at the end. i.e. host.procom.com -> 4host5procom3com0 + * Buffer space checking is done by caller. + * Parameters: + * ptr : address of pointer to buffer to store converted string + * zone: domain name string + * Returns: + * ptr: address of pointer to next available buffer space + * -1 : error + * 0 : success + */ +static int +dyndns_stuff_str(char **ptr, char *zone) +{ + int len; + char *lenPtr, *zonePtr; + + for (zonePtr = zone; *zonePtr; ) { + lenPtr = *ptr; + *ptr = *ptr + 1; + len = 0; + while (*zonePtr != '.' && *zonePtr != 0) { + *ptr = dyndns_put_byte(*ptr, *zonePtr); + zonePtr++; + len++; + } + *lenPtr = len; + if (*zonePtr == '.') + zonePtr++; + } + *ptr = dyndns_put_byte(*ptr, 0); + return (0); +} + +/* + * dyndns_build_header + * Build the header for DNS query and DNS update request message. + * Parameters: + * ptr : address of pointer to buffer to store header + * buf_len : buffer length + * msg_id : message id + * query_req : use REQ_QUERY for query message or REQ_UPDATE for + * update message + * quest_zone_cnt : number of question record for query message or + * number of zone record for update message + * ans_prereq_cnt : number of answer record for query message or + * number of prerequisite record for update message + * nameser_update_cnt: number of name server for query message or + * number of update record for update message + * addit_cnt : number of additional record + * flags : query flags word + * Returns: + * ptr: address of pointer to next available buffer space + * -1 : error + * 0 : success + */ +int +dyndns_build_header(char **ptr, int buf_len, uint16_t msg_id, int query_req, + uint16_t quest_zone_cnt, uint16_t ans_prereq_cnt, + uint16_t nameser_update_cnt, uint16_t addit_cnt, int flags) +{ + uint16_t opcode; + + if (buf_len < 12) { + syslog(LOG_ERR, "dyndns: no more buf for header section\n"); + return (-1); + } + + *ptr = dyndns_put_nshort(*ptr, msg_id); /* mesg ID */ + if (query_req == REQ_QUERY) + opcode = ns_o_query; /* query msg */ + else + opcode = ns_o_update << 11; /* update msg */ + opcode |= flags; + /* mesg opcode */ + *ptr = dyndns_put_nshort(*ptr, opcode); + /* zone record count */ + *ptr = dyndns_put_nshort(*ptr, quest_zone_cnt); + /* prerequiste record count */ + *ptr = dyndns_put_nshort(*ptr, ans_prereq_cnt); + /* update record count */ + *ptr = dyndns_put_nshort(*ptr, nameser_update_cnt); + /* additional record count */ + *ptr = dyndns_put_nshort(*ptr, addit_cnt); + + return (0); +} + +/* + * dyndns_build_quest_zone + * Build the question section for query message or zone section for + * update message. + * Parameters: + * ptr : address of pointer to buffer to store question or zone section + * buf_len: buffer length + * name : question or zone name + * type : type of question or zone + * class : class of question or zone + * Returns: + * ptr: address of pointer to next available buffer space + * -1 : error + * 0 : success + */ +int +dyndns_build_quest_zone(char **ptr, int buf_len, char *name, int type, + int class) +{ + char *zonePtr; + + if ((strlen(name) + 6) > buf_len) { + syslog(LOG_ERR, "dyndns: no more buf " + "for question/zone section\n"); + return (-1); + } + + zonePtr = *ptr; + (void) dyndns_stuff_str(&zonePtr, name); + *ptr = zonePtr; + *ptr = dyndns_put_nshort(*ptr, type); + *ptr = dyndns_put_nshort(*ptr, class); + return (0); +} + +/* + * dyndns_build_update + * Build update section of update message for adding and removing a record. + * If the ttl value is 0 then this message is for record deletion. + * + * Parameters: + * ptr : address of pointer to buffer to store update section + * buf_len : buffer length + * name : resource name of this record + * type : type of this record + * class : class of this record + * ttl : time-to-live, cached time of this entry by others and not + * within DNS database, a zero value for record(s) deletion + * data : data of this resource record + * forw_rev: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone + * add_del : UPDATE_ADD for adding entry, UPDATE_DEL for removing zone + * del_type: DEL_ONE for deleting one entry, DEL_ALL for deleting all + * entries of the same resource name. Only valid for UPDATE_DEL. + * Returns: + * ptr: address of pointer to next available buffer space + * -1 : error + * 0 : success + */ +static int +dyndns_build_update(char **ptr, int buf_len, char *name, int type, int class, + uint32_t ttl, char *data, int forw_rev, int add_del, int del_type) +{ + char *namePtr; + int rec_len, data_len; + + rec_len = strlen(name) + 10; + if (add_del == UPDATE_ADD) { + if (forw_rev == UPDATE_FORW) + data_len = 4; + else + data_len = strlen(data) + 2; + } else { + if (del_type == DEL_ALL) + data_len = 0; + else if (forw_rev == UPDATE_FORW) + data_len = 4; + else + data_len = strlen(data) + 2; + } + + if (rec_len + data_len > buf_len) { + syslog(LOG_ERR, "dyndns: no more buf for update section\n"); + return (-1); + } + + namePtr = *ptr; + (void) dyndns_stuff_str(&namePtr, name); + *ptr = namePtr; + *ptr = dyndns_put_nshort(*ptr, type); + *ptr = dyndns_put_nshort(*ptr, class); + *ptr = dyndns_put_nlong(*ptr, ttl); + + if (add_del == UPDATE_DEL && del_type == DEL_ALL) { + *ptr = dyndns_put_nshort(*ptr, 0); + return (0); + } + + if (forw_rev == UPDATE_FORW) { + *ptr = dyndns_put_nshort(*ptr, 4); + *ptr = dyndns_put_int(*ptr, inet_addr(data)); /* ip address */ + } else { + *ptr = dyndns_put_nshort(*ptr, strlen(data)+2); + namePtr = *ptr; + (void) dyndns_stuff_str(&namePtr, data); /* hostname */ + *ptr = namePtr; + } + return (0); +} + +/* + * dyndns_build_tkey + * Build TKEY section to establish security context for secure dynamic DNS + * update. DNS header and question sections need to be build before this + * section. The TKEY data are the tokens generated during security context + * establishment and the TKEY message is used to transmit those tokens, one + * at a time, to the DNS server. + * Parameters: + * ptr : address of pointer to buffer to store TKEY + * buf_len : buffer length + * name : key name, must be unique and same as for TSIG record + * key_expire: expiration time of this key in second + * data : TKEY data + * data_size : data size + * Returns: + * ptr: address of the pointer to the next available buffer space + * -1 : error + * 0 : success + */ +static int +dyndns_build_tkey(char **ptr, int buf_len, char *name, int key_expire, + char *data, int data_size) +{ + char *namePtr; + struct timeval tp; + + if (strlen(name)+2 + 45 + data_size > buf_len) { + syslog(LOG_ERR, "dyndns: no more buf for TKEY record\n"); + return (-1); + } + + namePtr = *ptr; + (void) dyndns_stuff_str(&namePtr, name); /* unique global name */ + *ptr = namePtr; + *ptr = dyndns_put_nshort(*ptr, ns_t_tkey); + *ptr = dyndns_put_nshort(*ptr, ns_c_any); + *ptr = dyndns_put_nlong(*ptr, 0); + /* 19 + 14 + data_size + 2 */ + *ptr = dyndns_put_nshort(*ptr, 35 + data_size); + namePtr = *ptr; + (void) dyndns_stuff_str(&namePtr, "gss.microsoft.com"); + *ptr = namePtr; + (void) gettimeofday(&tp, 0); + *ptr = dyndns_put_nlong(*ptr, tp.tv_sec); /* inception */ + /* expiration, 86400 */ + *ptr = dyndns_put_nlong(*ptr, tp.tv_sec + key_expire); + *ptr = dyndns_put_nshort(*ptr, MODE_GSS_API); /* mode: gss-api */ + *ptr = dyndns_put_nshort(*ptr, 0); /* error */ + *ptr = dyndns_put_nshort(*ptr, data_size); /* key size */ + (void) memcpy(*ptr, data, data_size); /* key data */ + *ptr += data_size; + *ptr = dyndns_put_nshort(*ptr, 0); /* other */ + return (0); +} + +/* + * dyndns_build_tsig + * Build TSIG section for secure dynamic DNS update. This routine will be + * called twice. First called with TSIG_UNSIGNED, and second with TSIG_SIGNED. + * The TSIG data is NULL and ignored for TSIG_UNSIGNED and is the update request + * message encrypted for TSIG_SIGNED. The message id must be the same id as the + * one in the update request before it is encrypted. + * Parameters: + * ptr : address of pointer to buffer to store TSIG + * buf_len : buffer length + * msg_id : message id + * name : key name, must be the same as in TKEY record + * fudge_time : amount of error time allow in seconds + * data : TSIG data if TSIG_SIGNED, otherwise NULL + * data_size : size of data, otherwise 0 if data is NULL + * data_signed: TSIG_SIGNED to indicate data is signed and encrypted, + * otherwise TSIG_UNSIGNED + * Returns: + * ptr: address of pointer to next available buffer space + * -1 : error + * 0 : success + */ +static int +dyndns_build_tsig(char **ptr, int buf_len, int msg_id, char *name, + int fudge_time, char *data, int data_size, int data_signed) +{ + char *namePtr; + struct timeval tp; + int signtime, fudge, rec_len; + + if (data_signed == TSIG_UNSIGNED) + rec_len = strlen(name)+2 + 37; + else + rec_len = strlen(name)+2 + 45 + data_size; + + if (rec_len > buf_len) { + syslog(LOG_ERR, "dyndns: no more buf for TSIG record\n"); + return (-1); + } + + namePtr = *ptr; + (void) dyndns_stuff_str(&namePtr, name); /* unique global name */ + *ptr = namePtr; + if (data_signed == TSIG_SIGNED) + *ptr = dyndns_put_nshort(*ptr, ns_t_tsig); + *ptr = dyndns_put_nshort(*ptr, ns_c_any); + *ptr = dyndns_put_nlong(*ptr, 0); + if (data_signed == TSIG_SIGNED) { + /* 19 + 10 + data_size + 6 */ + *ptr = dyndns_put_nshort(*ptr, 35 + data_size); + } + namePtr = *ptr; + (void) dyndns_stuff_str(&namePtr, "gss.microsoft.com"); + *ptr = namePtr; + (void) gettimeofday(&tp, 0); + signtime = tp.tv_sec >> 16; + *ptr = dyndns_put_nlong(*ptr, signtime); /* sign time */ + fudge = tp.tv_sec << 16; + fudge |= fudge_time; + *ptr = dyndns_put_nlong(*ptr, fudge); /* fudge time */ + if (data_signed == TSIG_SIGNED) { + /* signed data size */ + *ptr = dyndns_put_nshort(*ptr, data_size); + (void) memcpy(*ptr, data, data_size); /* signed data */ + *ptr += data_size; + *ptr = dyndns_put_nshort(*ptr, msg_id); /* original id */ + } + *ptr = dyndns_put_nshort(*ptr, 0); /* error */ + *ptr = dyndns_put_nshort(*ptr, 0); /* other */ + return (0); +} + +/* + * dyndns_open_init_socket + * This routine creates a SOCK_STREAM or SOCK_DGRAM socket and initializes it + * by doing bind() and setting linger option to off. + * + * Parameters: + * sock_type: SOCK_STREAM for TCP or SOCK_DGRAM for UDP + * dest_addr: destination address in network byte order + * port : destination port number + * Returns: + * descriptor: descriptor referencing the created socket + * -1 : error + */ +int +dyndns_open_init_socket(int sock_type, unsigned long dest_addr, int port) +{ + int s; + struct sockaddr_in my_addr; + struct linger l; + struct sockaddr_in serv_addr; + + if ((s = socket(AF_INET, sock_type, 0)) == -1) { + syslog(LOG_ERR, "dyndns: socket err\n"); + return (-1); + } + + l.l_onoff = 0; + if (setsockopt(s, SOL_SOCKET, SO_LINGER, + (char *)&l, sizeof (l)) == -1) { + syslog(LOG_ERR, "dyndns: setsocket err\n"); + (void) close(s); + return (-1); + } + + bzero(&my_addr, sizeof (my_addr)); + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(0); + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(s, (struct sockaddr *)&my_addr, sizeof (my_addr)) < 0) { + syslog(LOG_ERR, "dyndns: client bind err\n"); + (void) close(s); + return (-1); + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(port); + serv_addr.sin_addr.s_addr = dest_addr; + + if (connect(s, (struct sockaddr *)&serv_addr, + sizeof (struct sockaddr_in)) < 0) { + syslog(LOG_ERR, "dyndns: client connect err (%s)\n", + strerror(errno)); + (void) close(s); + return (-1); + } + + return (s); +} + +/* + * dyndns_acquire_cred + * This routine is used to acquire a GSS credential handle to a user's Kerberos + * ticket-granting ticket (TGT) stored locally on the system. If getting a + * handle fails, then a new TGT will be obtained again before trying to get a + * handle once more. + * The user's password is taken from the environment variable + * lookup.dns.dynamic.passwd and is encrypted. + * Paramaters: + * kinit_retry: if 0 then a new TGT can be obtained before second attempt to + * get a handle to TGT if first attempt fails + * Returns: + * user_name : name of user to get credential handle from + * credHandle : handle to user's credential (TGT) + * oid : contains Kerberos 5 object identifier + * kinit_retry: 1 if a new TGT has been acquired in this routine, otherwise 0 + * -1 : error + */ +static int +dyndns_acquire_cred(gss_cred_id_t *credHandle, char *user_name, + gss_OID *oid, int *kinit_retry) +{ + char *p, pwd[100]; + + smb_config_rdlock(); + p = smb_config_getstr(SMB_CI_ADS_USER); + if (p == NULL || *p == 0) { + syslog(LOG_ERR, "No user configured for " + "secure dynamic DNS update.\n"); + smb_config_unlock(); + return (-1); + } + (void) strcpy(user_name, p); + + p = smb_config_getstr(SMB_CI_ADS_PASSWD); + if (p == NULL || *p == 0) { + syslog(LOG_ERR, "No password configured for " + "secure dynamic DNS update.\n"); + smb_config_unlock(); + return (-1); + } + smb_config_unlock(); + + (void) strcpy(pwd, p); + + return krb5_acquire_cred_kinit(user_name, pwd, credHandle, + oid, kinit_retry, "dyndns"); +} + +/* + * dyndns_build_tkey_msg + * This routine is used to build the TKEY message to transmit GSS tokens + * during GSS security context establishment for secure DNS update. The + * TKEY message format uses the DNS query message format. The TKEY section + * is the answer section of the query message format. + * Microsoft uses a value of 86400 seconds (24 hours) for key expiration time. + * Parameters: + * buf : buffer to build and store TKEY message + * key_name: a unique key name, this same key name must be also be used in + * the TSIG message + * out_tok : TKEY message data (GSS tokens) + * Returns: + * id : message id of this TKEY message + * message size: the size of the TKEY message + * -1 : error + */ +static int +dyndns_build_tkey_msg(char *buf, char *key_name, uint16_t *id, + gss_buffer_desc *out_tok) +{ + int queryReq, zoneCount, preqCount, updateCount, additionalCount; + int zoneType, zoneClass; + char *bufptr; + + queryReq = REQ_QUERY; + /* query section of query request */ + zoneCount = 1; + /* answer section of query request */ + preqCount = 1; + updateCount = 0; + additionalCount = 0; + + (void) memset(buf, 0, MAX_TCP_SIZE); + bufptr = buf; + *id = smb_get_next_resid(); + + /* add TCP length info that follows this field */ + bufptr = dyndns_put_nshort(bufptr, + 26 + (strlen(key_name)+2)*2 + 35 + out_tok->length); + + if (dyndns_build_header(&bufptr, BUFLEN_TCP(bufptr, buf), *id, queryReq, + zoneCount, preqCount, updateCount, additionalCount, 0) == -1) { + return (-1); + } + + zoneType = ns_t_tkey; + zoneClass = ns_c_in; + if (dyndns_build_quest_zone(&bufptr, BUFLEN_TCP(bufptr, buf), key_name, + zoneType, zoneClass) == -1) { + return (-1); + } + + if (dyndns_build_tkey(&bufptr, BUFLEN_TCP(bufptr, buf), key_name, + 86400, out_tok->value, out_tok->length) == -1) { + return (-1); + } + + return (bufptr - buf); +} + +/* + * dyndns_establish_sec_ctx + * This routine is used to establish a security context with the DNS server + * by building TKEY messages and sending them to the DNS server. TKEY messages + * are also received from the DNS server for processing. The security context + * establishment is done with the GSS client on the system producing a token + * and sending the token within the TKEY message to the GSS server on the DNS + * server. The GSS server then processes the token and then send a TKEY reply + * message with a new token to be processed by the GSS client. The GSS client + * processes the new token and then generates a new token to be sent to the + * GSS server. This cycle is continued until the security establishment is + * done. TCP is used to send and receive TKEY messages. + * If gss_init_sec_context fails then a new TGT will be acquired so that + * security establishment can be retry once more by the caller after getting + * a handle to the new TGT (credential). + * Parameters: + * credHandle : handle to credential + * s : socket descriptor to DNS server + * key_name : TKEY key name + * dns_hostname: fully qualified DNS hostname + * oid : contains Kerberos 5 object identifier + * user_name : name of user to perform DNS update + * kinit_retry : if 0 and gss_init_sec_context fails then get new TGT so + * the caller can restart doing security context establishment + * Returns: + * gss_context : handle to security context + * kinit_retry : 1 if a new TGT has been acquired in this routine, + * otherwise 0 + * do_acquire_cred: if 1 then caller will restart security context + * establishment + * -1 : error + */ +static int +dyndns_establish_sec_ctx(gss_ctx_id_t *gss_context, gss_cred_id_t credHandle, + int s, char *key_name, char *dns_hostname, gss_OID oid, char *user_name, + int *kinit_retry, int *do_acquire_cred) +{ + uint16_t id, rid, rsz; + char buf[MAX_TCP_SIZE], buf2[MAX_TCP_SIZE]; + int ret; + char *service_name, *tmpptr; + int service_sz; + OM_uint32 min, maj, time_rec; + gss_buffer_desc service_buf, in_tok, out_tok; + gss_name_t target_name; + gss_buffer_desc *inputptr; + int gss_flags; + OM_uint32 ret_flags; + int buf_sz; + char *p, pwd[100]; + + smb_config_rdlock(); + p = smb_config_getstr(SMB_CI_ADS_PASSWD); + if (p == NULL || *p == 0) { + syslog(LOG_ERR, "No password configured for " + "secure dynamic DNS update.\n"); + smb_config_unlock(); + return (-1); + } + smb_config_unlock(); + (void) strcpy(pwd, p); + + service_sz = strlen(dns_hostname) + 5; + service_name = (char *)malloc(sizeof (char) * service_sz); + if (service_name == NULL) { + syslog(LOG_ERR, "Malloc failed for %d bytes ", service_sz); + smb_config_unlock(); + return (-1); + } + (void) snprintf(service_name, service_sz, "DNS@%s", dns_hostname); + service_buf.value = service_name; + service_buf.length = strlen(service_name)+1; + if ((maj = gss_import_name(&min, &service_buf, + (gss_OID) gss_nt_service_name, + &target_name)) != GSS_S_COMPLETE) { + display_stat(maj, min); + (void) gss_release_oid(&min, &oid); + (void) free(service_name); + return (-1); + } + (void) free(service_name); + + inputptr = GSS_C_NO_BUFFER; + *gss_context = GSS_C_NO_CONTEXT; + gss_flags = GSS_C_MUTUAL_FLAG | GSS_C_DELEG_FLAG | GSS_C_REPLAY_FLAG | + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; + do { + if (krb5_establish_sec_ctx_kinit(user_name, pwd, credHandle, + gss_context, target_name, oid, gss_flags, inputptr, + &out_tok, &ret_flags, &time_rec, kinit_retry, + do_acquire_cred, &maj, "dyndns") == -1) { + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + return (-1); + } + + if ((maj == GSS_S_COMPLETE) && + !(ret_flags & GSS_C_REPLAY_FLAG)) { + syslog(LOG_ERR, "dyndns: No GSS_C_REPLAY_FLAG\n"); + if (out_tok.length > 0) + (void) gss_release_buffer(&min, &out_tok); + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + return (-1); + } + + if ((maj == GSS_S_COMPLETE) && + !(ret_flags & GSS_C_MUTUAL_FLAG)) { + syslog(LOG_ERR, "dyndns: No GSS_C_MUTUAL_FLAG\n"); + if (out_tok.length > 0) + (void) gss_release_buffer(&min, &out_tok); + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + return (-1); + } + + if (out_tok.length > 0) { + if ((buf_sz = dyndns_build_tkey_msg(buf, key_name, + &id, &out_tok)) <= 0) { + (void) gss_release_buffer(&min, &out_tok); + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + return (-1); + } + + (void) gss_release_buffer(&min, &out_tok); + + if (send(s, buf, buf_sz, 0) == -1) { + syslog(LOG_ERR, "dyndns: TKEY send error\n"); + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + return (-1); + } + + bzero(buf2, MAX_TCP_SIZE); + if (recv(s, buf2, MAX_TCP_SIZE, 0) == -1) { + syslog(LOG_ERR, "dyndns: TKEY " + "reply recv error\n"); + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + return (-1); + } + + ret = buf2[5] & 0xf; /* error field in TCP */ + if (ret != NOERROR) { + syslog(LOG_ERR, "dyndns: Error in " + "TKEY reply: %d: ", ret); + dyndns_msg_err(ret); + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + return (-1); + } + + tmpptr = &buf2[2]; + (void) dyndns_get_nshort(tmpptr, &rid); + if (id != rid) { + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + return (-1); + } + + tmpptr = &buf2[59+(strlen(key_name)+2)*2]; + (void) dyndns_get_nshort(tmpptr, &rsz); + in_tok.length = rsz; + + /* bsd38 -> 2*7=14 */ + in_tok.value = &buf2[61+(strlen(key_name)+2)*2]; + inputptr = &in_tok; + } + + } while (maj != GSS_S_COMPLETE); + + (void) gss_release_oid(&min, &oid); + (void) gss_release_name(&min, &target_name); + + return (0); +} + +/* + * dyndns_get_sec_context + * Get security context for secure dynamic DNS update. This routine opens + * a TCP socket to the DNS server and calls routines to get a handle to a + * locally cached user's credential and establish a security context with + * the DNS server to perform secure dynamic DNS update. If getting security + * context fails then a retry may be done after reobtaining new credential and + * getting a new credential handle. If obtaining new credential has been + * done earlier during getting a handle to credential then there is no need to + * do a retry for security context. + * Parameters: + * hostname: fully qualified hostname + * dns_ip : ip address of hostname in network byte order + * Returns: + * gss_handle: gss credential handle + * gss_context: gss security context + * -1: error + * 0: success + */ +static gss_ctx_id_t +dyndns_get_sec_context(const char *hostname, int dns_ip) +{ + int s; + gss_cred_id_t credHandle; + gss_ctx_id_t gss_context; + gss_OID oid; + OM_uint32 min; + struct hostent *hentry; + int kinit_retry, do_acquire_cred; + char *key_name, dns_hostname[255], user_name[50]; + + key_name = (char *)hostname; + + hentry = gethostbyaddr((char *)&dns_ip, 4, AF_INET); + if (hentry == NULL) { + syslog(LOG_ERR, "dyndns: Can't get DNS " + "hostname from DNS ip.\n"); + return (NULL); + } + (void) strcpy(dns_hostname, hentry->h_name); + + if ((s = dyndns_open_init_socket(SOCK_STREAM, dns_ip, 53)) < 0) { + return (NULL); + } + + kinit_retry = 0; + do_acquire_cred = 0; + acquire_cred: + + if (dyndns_acquire_cred(&credHandle, user_name, &oid, &kinit_retry)) { + (void) close(s); + return (NULL); + } + + if (dyndns_establish_sec_ctx(&gss_context, credHandle, s, key_name, + dns_hostname, oid, user_name, &kinit_retry, &do_acquire_cred)) { + (void) gss_release_cred(&min, &credHandle); + if (do_acquire_cred) { + do_acquire_cred = 0; + goto acquire_cred; + } + (void) close(s); + return (NULL); + } + + (void) close(s); + + (void) gss_release_cred(&min, &credHandle); + return (gss_context); +} + +/* + * dyndns_build_add_remove_msg + * This routine builds the update request message for adding and removing DNS + * entries which is used for non-secure and secure DNS update. + * This routine builds an UDP message. + * Parameters: + * buf : buffer to build message + * update_zone: the type of zone to update, use UPDATE_FORW for forward + * lookup zone, use UPDATE_REV for reverse lookup zone + * hostname : fully qualified hostname to update DNS with + * ip_addr : IP address of hostname + * life_time : cached time of this entry by others and not within DNS + * database + * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry + * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all + * entries of the same resource name. Only valid for UPDATE_DEL. + * addit_cnt : Indicate how many record is in the additional section of + * the DNS message. A value of zero is always used with + * non-secure update message. For secure update message, + * the value will be one because the signed TSIG message + * is added as the additional record of the DNS update message. + * id : DNS message ID. If a positive value then this ID value is + * used, otherwise the next incremented value is used + * level : This is the domain level which we send the request to, level + * zero is the default level, it can go upto 2 in reverse zone + * and virtually to any level in forward zone. + * Returns: + * buf : buffer containing update message + * id : DNS message ID + * int : size of update message + * -1 : error + * + * This function is changed to handle dynamic DNS update retires to higher + * authoritative domains. + */ +static int +dyndns_build_add_remove_msg(char *buf, int update_zone, const char *hostname, + const char *ip_addr, int life_time, int update_type, int del_type, + int addit_cnt, uint16_t *id, int level) +{ + int a, b, c, d; + char *bufptr; + int queryReq, zoneCount, preqCount, updateCount, additionalCount; + char *zone, *resource, *data, zone_buf[100], resrc_buf[100]; + int zoneType, zoneClass, type, class, ttl; + char *p; + + queryReq = REQ_UPDATE; + zoneCount = 1; + preqCount = 0; + updateCount = 1; + additionalCount = addit_cnt; + + (void) memset(buf, 0, NS_PACKETSZ); + bufptr = buf; + + if (*id == 0) + *id = smb_get_next_resid(); + + if (dyndns_build_header(&bufptr, BUFLEN_UDP(bufptr, buf), *id, queryReq, + zoneCount, preqCount, updateCount, additionalCount, 0) == -1) { + return (-1); + } + + zoneType = ns_t_soa; + zoneClass = ns_c_in; + + if (update_zone == UPDATE_FORW) { + p = (char *)hostname; + + /* Try higher domains according to the level requested */ + do { + /* domain */ + if ((zone = (char *)strchr(p, '.')) == NULL) + return (-1); + zone += 1; + p = zone; + } while (--level >= 0); + resource = (char *)hostname; + data = (char *)ip_addr; + } else { + (void) sscanf(ip_addr, "%d.%d.%d.%d", &a, &b, &c, &d); + (void) sprintf(zone_buf, "%d.%d.%d.in-addr.arpa", c, b, a); + zone = p = zone_buf; + + /* Try higher domains according to the level requested */ + while (--level >= 0) { + /* domain */ + if ((zone = (char *)strchr(p, '.')) == NULL) { + return (-1); + } + zone += 1; + p = zone; + } + + (void) sprintf(resrc_buf, "%d.%d.%d.%d.in-addr.arpa", + d, c, b, a); + resource = resrc_buf; /* ip info */ + data = (char *)hostname; + } + + if (dyndns_build_quest_zone(&bufptr, BUFLEN_UDP(bufptr, buf), zone, + zoneType, zoneClass) == -1) { + return (-1); + } + + if (update_zone == UPDATE_FORW) + type = ns_t_a; + else + type = ns_t_ptr; + + if (update_type == UPDATE_ADD) { + class = ns_c_in; + ttl = life_time; + } else { + if (del_type == DEL_ONE) + class = ns_c_none; /* remove one */ + else + class = ns_c_any; /* remove all */ + ttl = 0; + } + if (dyndns_build_update(&bufptr, BUFLEN_UDP(bufptr, buf), + resource, type, class, ttl, data, update_zone, + update_type, del_type) == -1) { + return (-1); + } + + return (bufptr - buf); +} + +/* + * dyndns_build_unsigned_tsig_msg + * This routine is used to build the unsigned TSIG message for signing. The + * unsigned TSIG message contains the update request message with certain TSIG + * fields included. An error time of 300 seconds is used for fudge time. This + * is the number used by Microsoft clients. + * This routine builds a UDP message. + * Parameters: + * buf : buffer to build message + * update_zone: the type of zone to update, use UPDATE_FORW for forward + * lookup zone, use UPDATE_REV for reverse lookup zone + * hostname : fully qualified hostname to update DNS with + * ip_addr : IP address of hostname + * life_time : cached time of this entry by others and not within DNS + * database + * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry + * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all + * entries of the same resource name. Only valid for UPDATE_DEL. + * key_name : same key name used in TKEY message + * id : DNS message ID. If a positive value then this ID value is + * used, otherwise the next incremented value is used + * level : This is the domain level which we send the request to, level + * zero is the default level, it can go upto 2 in reverse zone + * and virtually to any level in forward zone. + * Returns: + * buf : buffer containing update message + * id : DNS message ID + * int : size of update message + * -1 : error + */ +static int +dyndns_build_unsigned_tsig_msg(char *buf, int update_zone, const char *hostname, + const char *ip_addr, int life_time, int update_type, int del_type, + char *key_name, uint16_t *id, int level) +{ + char *bufptr; + int buf_sz; + + if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname, + ip_addr, life_time, update_type, del_type, 0, id, level)) <= 0) { + return (-1); + } + + bufptr = buf + buf_sz; + + if (dyndns_build_tsig(&bufptr, BUFLEN_UDP(bufptr, buf), 0, + key_name, 300, NULL, 0, TSIG_UNSIGNED) == -1) { + return (-1); + } + + return (bufptr - buf); +} + +/* + * dyndns_build_signed_tsig_msg + * This routine build the signed TSIG message which contains the update + * request message encrypted. An error time of 300 seconds is used for fudge + * time. This is the number used by Microsoft clients. + * This routine builds a UDP message. + * Parameters: + * buf : buffer to build message + * update_zone: the type of zone to update, use UPDATE_FORW for forward + * lookup zone, use UPDATE_REV for reverse lookup zone + * hostname : fully qualified hostname to update DNS with + * ip_addr : IP address of hostname + * life_time : cached time of this entry by others and not within DNS + * database + * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry + * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all + * entries of the same resource name. Only valid for UPDATE_DEL. + * key_name : same key name used in TKEY message + * id : DNS message ID. If a positive value then this ID value is + * used, otherwise the next incremented value is used + * in_mic : the update request message encrypted + * level : This is the domain level which we send the request to, level + * zero is the default level, it can go upto 2 in reverse zone + * and virtually to any level in forward zone. + * + * Returns: + * buf : buffer containing update message + * id : DNS message ID + * int : size of update message + * -1 : error + */ +static int +dyndns_build_signed_tsig_msg(char *buf, int update_zone, const char *hostname, + const char *ip_addr, int life_time, int update_type, int del_type, + char *key_name, uint16_t *id, gss_buffer_desc *in_mic, int level) +{ + char *bufptr; + int buf_sz; + + if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname, + ip_addr, life_time, update_type, del_type, 1, id, level)) <= 0) { + return (-1); + } + + bufptr = buf + buf_sz; + + if (dyndns_build_tsig(&bufptr, BUFLEN_UDP(bufptr, buf), + *id, key_name, 300, in_mic->value, + in_mic->length, TSIG_SIGNED) == -1) { + return (-1); + } + + return (bufptr - buf); +} + +/* + * dyndns_udp_send_recv + * This routine sends and receives UDP DNS request and reply messages. Time + * out value and retry count is indicated by two environment variables: + * lookup_dns_retry_cnt + * lookup_dns_retry_sec + * If either of these two variables are undefined or their value exceed the + * value of 10 then a default value of 3 retry and/or a default value of 3 + * secs are used. + * + * Pre-condition: Caller must call dyndns_open_init_socket() before calling + * this function. + * + * Parameters: + * s : socket descriptor + * buf : buffer containing data to send + * buf_sz : size of data to send + * Returns: + * -1 : error + * rec_buf: reply dat + * 0 : success + */ +int +dyndns_udp_send_recv(int s, char *buf, int buf_sz, char *rec_buf) +{ + int i, retval, addr_len, max_retries; + struct timeval tv, timeout; + fd_set rfds; + struct sockaddr_in from_addr; + char *p; + + smb_config_rdlock(); + p = smb_config_getstr(SMB_CI_DYNDNS_RETRY_COUNT); + if (p == NULL || *p == 0) { + max_retries = 3; + } else { + max_retries = atoi(p); + if (max_retries < 1 || max_retries > 10) + max_retries = 3; + } + + p = smb_config_getstr(SMB_CI_DYNDNS_RETRY_SEC); + timeout.tv_usec = 0; + if (p == NULL || *p == 0) { + timeout.tv_sec = 3; + } else { + timeout.tv_sec = atoi(p); + if (timeout.tv_sec < 1 || timeout.tv_sec > 10) + timeout.tv_sec = 3; + } + smb_config_unlock(); + + for (i = 0; i < max_retries + 1; i++) { + if (send(s, buf, buf_sz, 0) == -1) { + syslog(LOG_ERR, "dyndns: UDP send error (%s)\n", + strerror(errno)); + return (-1); + } + + FD_ZERO(&rfds); + FD_SET(s, &rfds); + + tv = timeout; + + retval = select(s+1, &rfds, NULL, NULL, &tv); + + if (retval == -1) { + return (-1); + } else if (retval > 0) { + bzero(rec_buf, NS_PACKETSZ); + /* required by recvfrom */ + addr_len = sizeof (struct sockaddr_in); + if (recvfrom(s, rec_buf, NS_PACKETSZ, 0, + (struct sockaddr *)&from_addr, &addr_len) == -1) { + syslog(LOG_ERR, "dyndns: UDP recv err\n"); + return (-1); + } + break; + } + } + + if (i == (max_retries + 1)) { /* did not receive anything */ + syslog(LOG_ERR, "dyndns: max retries for UDP recv reached\n"); + return (-1); + } + + return (0); +} + +/* + * dyndns_sec_add_remove_entry + * Perform secure dynamic DNS update after getting security context. + * This routine opens a UDP socket to the DNS sever, gets the security context, + * builds the unsigned TSIG message and signed TSIG message. The signed TSIG + * message containing the encrypted update request message is sent to the DNS + * server. The response is received and check for error. If there is no + * error then credential handle and security context are released and the local + * NSS cached is purged. + * Parameters: + * update_zone : UPDATE_FORW for forward zone, UPDATE_REV for reverse zone + * hostname : fully qualified hostname + * ip_addr : ip address of hostname in string format + * life_time : cached time of this entry by others and not within DNS + * database + * max_retries : maximum retries for sending DNS update request + * recv_timeout: receive timeout + * update_type : UPDATE_ADD for adding entry, UPDATE_DEL for removing entry + * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all + * entries of the same resource name. Only valid for UPDATE_DEL + * dns_str : DNS IP address in string format + * Returns: + * -1: error + * 0: success + * + * This function is enhanced to handle the case of NOTAUTH error when DNS server + * is not authoritative for specified zone. In this case we need to resend the + * same request to the higher authoritative domains. + * This is true for both secure and unsecure dynamic DNS updates. + */ +static int +dyndns_sec_add_remove_entry(int update_zone, const char *hostname, + const char *ip_addr, int life_time, int update_type, int del_type, + char *dns_str) +{ + int s2; + uint16_t id, rid; + char buf[NS_PACKETSZ], buf2[NS_PACKETSZ]; + int ret; + OM_uint32 min, maj; + gss_buffer_desc in_mic, out_mic; + gss_ctx_id_t gss_context; + int dns_ip; + char *key_name; + int buf_sz; + int level = 0; + + assert(dns_str); + assert(*dns_str); + + dns_ip = inet_addr(dns_str); + +sec_retry_higher: + + if ((gss_context = dyndns_get_sec_context(hostname, + dns_ip)) == NULL) { + return (-1); + } + + key_name = (char *)hostname; + + if ((s2 = dyndns_open_init_socket(SOCK_DGRAM, dns_ip, 53)) < 0) { + (void) gss_delete_sec_context(&min, &gss_context, NULL); + return (-1); + } + + id = 0; + if ((buf_sz = dyndns_build_unsigned_tsig_msg(buf, update_zone, hostname, + ip_addr, life_time, update_type, del_type, + key_name, &id, level)) <= 0) { + (void) close(s2); + (void) gss_delete_sec_context(&min, &gss_context, NULL); + return (-1); + } + + in_mic.length = buf_sz; + in_mic.value = buf; + + /* sign update message */ + if ((maj = gss_get_mic(&min, gss_context, 0, &in_mic, &out_mic)) != + GSS_S_COMPLETE) { + display_stat(maj, min); + (void) close(s2); + (void) gss_delete_sec_context(&min, &gss_context, NULL); + return (-1); + } + + if ((buf_sz = dyndns_build_signed_tsig_msg(buf, update_zone, hostname, + ip_addr, life_time, update_type, del_type, key_name, &id, + &out_mic, level)) <= 0) { + (void) close(s2); + (void) gss_release_buffer(&min, &out_mic); + (void) gss_delete_sec_context(&min, &gss_context, NULL); + return (-1); + } + + (void) gss_release_buffer(&min, &out_mic); + + if (dyndns_udp_send_recv(s2, buf, buf_sz, buf2)) { + (void) close(s2); + (void) gss_delete_sec_context(&min, &gss_context, NULL); + return (-1); + } + + (void) close(s2); + + (void) gss_delete_sec_context(&min, &gss_context, NULL); + + ret = buf2[3] & 0xf; /* error field in UDP */ + + /* + * If it is a NOTAUTH error we should retry with higher domains + * until we get a successful reply or the maximum retries is met. + */ + if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES) + goto sec_retry_higher; + + /* check here for update request is successful */ + if (ret != NOERROR) { + syslog(LOG_ERR, "dyndns: Error in TSIG reply: %d: ", ret); + dyndns_msg_err(ret); + return (-1); + } + + (void) dyndns_get_nshort(buf2, &rid); + if (id != rid) + return (-1); + + return (0); +} + +/* + * dyndns_seach_entry + * Query DNS server for entry. This routine can indicate if an entry exist + * or not during forward or reverse lookup. Also can indicate if the data + * of the entry matched. For example, for forward lookup, the entry is + * searched using the hostname and the data is the IP address. For reverse + * lookup, the entry is searched using the IP address and the data is the + * hostname. + * Parameters: + * update_zone: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone + * hostname : fully qualified hostname + * ip_addr : ip address of hostname in string format + * update_type: UPDATE_ADD for adding entry, UPDATE_DEL for removing entry + * Returns: + * time_out: no use + * is_match: is 1 for found matching entry, otherwise 0 + * 1 : an entry exist but not necessarily match + * 0 : an entry does not exist + */ +/*ARGSUSED*/ +static int +dyndns_search_entry(int update_zone, const char *hostname, const char *ip_addr, + int update_type, struct timeval *time_out, int *is_match) +{ + struct hostent *hentry; + struct in_addr in; + in_addr_t ip; + int i; + + *is_match = 0; + if (update_zone == UPDATE_FORW) { + hentry = gethostbyname(hostname); + if (hentry) { + ip = inet_addr(ip_addr); + for (i = 0; hentry->h_addr_list[i]; i++) { + (void) memcpy(&in.s_addr, + hentry->h_addr_list[i], sizeof (in.s_addr)); + if (ip == in.s_addr) { + *is_match = 1; + break; + } + } + return (1); + } + } else { + int dns_ip = inet_addr(ip_addr); + hentry = gethostbyaddr((char *)&dns_ip, 4, AF_INET); + if (hentry) { + if (strncasecmp(hentry->h_name, hostname, + strlen(hostname)) == 0) { + *is_match = 1; + } + return (1); + } + } + + /* entry does not exist */ + return (0); +} + +/* + * dyndns_add_remove_entry + * Perform non-secure dynamic DNS update. If fail then tries secure update. + * This routine opens a UDP socket to the DNS sever, build the update request + * message, and sends the message to the DNS server. The response is received + * and check for error. If there is no error then the local NSS cached is + * purged. DNS may be used to check to see if an entry already exist before + * adding or to see if an entry does exist before removing it. Adding + * duplicate entries or removing non-existing entries does not cause any + * problems. DNS is not check when doing a delete all. + * Parameters: + * update_zone: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone + * hostname : fully qualified hostname + * ip_addr : ip address of hostname in string format + * life_time : cached time of this entry by others and not within DNS + * database + * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry + * do_check : DNS_CHECK to check first in DNS, DNS_NOCHECK for no DNS + * checking before update + * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all + * entries of the same resource name. Only valid for UPDATE_DEL. + * dns_str : DNS IP address in string format + * Returns: + * -1: error + * 0: success + * + * This function is enhanced to handle the case of NOTAUTH error when DNS server + * is not authoritative for specified zone. In this case we need to resend the + * same request to the higher authoritative domains. + * This is true for both secure and unsecure dynamic DNS updates. + */ +static int +dyndns_add_remove_entry(int update_zone, const char *hostname, + const char *ip_addr, int life_time, int update_type, + int do_check, int del_type, char *dns_str) +{ + int s; + uint16_t id, rid; + char buf[NS_PACKETSZ], buf2[NS_PACKETSZ]; + int ret, dns_ip; + int is_exist, is_match; + struct timeval timeout; + int buf_sz; + int level = 0; + + assert(dns_str); + assert(*dns_str); + + dns_ip = inet_addr(dns_str); + + if (do_check == DNS_CHECK && del_type != DEL_ALL) { + is_exist = dyndns_search_entry(update_zone, hostname, ip_addr, + update_type, &timeout, &is_match); + + if (update_type == UPDATE_ADD && is_exist && is_match) { + return (0); + } else if (update_type == UPDATE_DEL && !is_exist) { + return (0); + } + } + +retry_higher: + if ((s = dyndns_open_init_socket(SOCK_DGRAM, dns_ip, 53)) < 0) { + return (-1); + } + + id = 0; + if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname, + ip_addr, life_time, update_type, del_type, 0, &id, level)) <= 0) { + (void) close(s); + return (-1); + } + + if (dyndns_udp_send_recv(s, buf, buf_sz, buf2)) { + (void) close(s); + return (-1); + } + + (void) close(s); + + ret = buf2[3] & 0xf; /* error field in UDP */ + + /* + * If it is a NOTAUTH error we should retry with higher domains + * until we get a successful reply + */ + if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES) + goto retry_higher; + + /* check here for update request is successful */ + if (ret == NOERROR) { + (void) dyndns_get_nshort(buf2, &rid); + if (id != rid) + return (-1); + return (0); + } + + if (ret == NOTIMP) { + syslog(LOG_ERR, "dyndns: DNS does not " + "support dynamic update\n"); + return (-1); + } else if (ret == NOTAUTH) { + syslog(LOG_ERR, "dyndns: DNS is not authoritative for " + "zone name in zone section\n"); + return (-1); + } + + ret = dyndns_sec_add_remove_entry(update_zone, hostname, + ip_addr, life_time, update_type, del_type, dns_str); + + return (ret); +} + +/* + * dyndns_add_entry + * Main routine to add an entry into DNS. The attempt will be made on the + * the servers returned by smb_get_nameserver(). Upon a successful + * attempt on any one of the server, the function will exit with 0. + * Otherwise, -1 is retuned to indicate the update attempt on all the + * nameservers has failed. + * + * Parameters: + * update_zone: the type of zone to update, use UPDATE_FORW for forward + * lookup zone, use UPDATE_REV for reverse lookup zone + * hostname : fully qualified hostname + * ip_addr : ip address of hostname in string format + * life_time : cached time of this entry by others and not within DNS + * database + * Returns: + * -1: error + * 0: success + */ +static int +dyndns_add_entry(int update_zone, const char *hostname, const char *ip_addr, + int life_time) +{ + char *dns_str; + struct in_addr ns_list[MAXNS]; + int i, cnt; + int addr, rc = 0; + + if (hostname == NULL || ip_addr == NULL) { + return (-1); + } + + addr = (int)inet_addr(ip_addr); + if ((addr == -1) || (addr == 0)) { + return (-1); + } + + cnt = smb_get_nameservers(ns_list, MAXNS); + + for (i = 0; i < cnt; i++) { + dns_str = inet_ntoa(ns_list[i]); + if ((dns_str == NULL) || + (strcmp(dns_str, "0.0.0.0") == 0)) { + continue; + } + + if (update_zone == UPDATE_FORW) { + syslog(LOG_DEBUG, "Dynamic update on forward lookup " + "zone for %s (%s)...\n", hostname, ip_addr); + } else { + syslog(LOG_DEBUG, "Dynamic update on reverse lookup " + "zone for %s (%s)...\n", hostname, ip_addr); + } + if (dyndns_add_remove_entry(update_zone, hostname, + ip_addr, life_time, + UPDATE_ADD, DNS_NOCHECK, DEL_NONE, dns_str) != -1) { + rc = 1; + break; + } + } + + return (rc ? 0 : -1); +} + +/* + * dyndns_remove_entry + * Main routine to remove an entry or all entries of the same resource name + * from DNS. The update attempt will be made on the primary DNS server. If + * there is a failure then another attempt will be made on the secondary DNS + * server. + * Parameters: + * update_zone: the type of zone to update, use UPDATE_FORW for forward + * lookup zone, use UPDATE_REV for reverse lookup zone + * hostname : fully qualified hostname + * ip_addr : ip address of hostname in string format + * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all + * entries of the same resource name. Only valid for UPDATE_DEL + * Returns: + * -1: error + * 0: success + */ +static int +dyndns_remove_entry(int update_zone, const char *hostname, const char *ip_addr, + int del_type) +{ + char *dns_str; + struct in_addr ns_list[MAXNS]; + int i, cnt, scnt; + int addr; + + if ((hostname == NULL || ip_addr == NULL)) { + return (-1); + } + + addr = (int)inet_addr(ip_addr); + if ((addr == -1) || (addr == 0)) { + return (-1); + } + + cnt = smb_get_nameservers(ns_list, MAXNS); + scnt = 0; + + for (i = 0; i < cnt; i++) { + dns_str = inet_ntoa(ns_list[i]); + if ((dns_str == NULL) || + (strcmp(dns_str, "0.0.0.0") == 0)) { + continue; + } + + if (update_zone == UPDATE_FORW) { + if (del_type == DEL_ONE) { + syslog(LOG_DEBUG, "Dynamic update " + "on forward lookup " + "zone for %s (%s)...\n", hostname, ip_addr); + } else { + syslog(LOG_DEBUG, "Removing all " + "entries of %s " + "in forward lookup zone...\n", hostname); + } + } else { + if (del_type == DEL_ONE) { + syslog(LOG_DEBUG, "Dynamic update " + "on reverse lookup " + "zone for %s (%s)...\n", hostname, ip_addr); + } else { + syslog(LOG_DEBUG, "Removing all " + "entries of %s " + "in reverse lookup zone...\n", ip_addr); + } + } + if (dyndns_add_remove_entry(update_zone, hostname, ip_addr, 0, + UPDATE_DEL, DNS_NOCHECK, del_type, dns_str) != -1) { + scnt++; + break; + } + } + if (scnt) + return (0); + return (-1); +} + +/* + * dyndns_update + * Perform dynamic update on both forward and reverse lookup zone using + * current hostname and IP addresses. Before updating DNS, existing host + * entries with the same hostname in the forward lookup zone are removed + * and existing pointer entries with the same IP addresses in the reverse + * lookup zone are removed. After DNS update, host entries for current + * hostname will show current IP addresses and pointer entries for current + * IP addresses will show current hostname. + * Parameters: + * None + * Returns: + * -1: some dynamic DNS updates errors + * 0: successful + */ +int +dyndns_update(void) +{ + int i, forw_update_ok, error; + char fqdn[MAXHOSTNAMELEN]; + char *my_ip; + int nc_cnt; + struct in_addr addr; + int rc; + + if (!dyndns_enabled()) + return (-1); + + if (smb_getfqhostname(fqdn, MAXHOSTNAMELEN) != 0) + return (-1); + + nc_cnt = smb_nic_get_num(); + + error = 0; + forw_update_ok = 0; + + /* + * Dummy IP is okay since we are removing all using the hostname. + */ + if (dyndns_remove_entry(UPDATE_FORW, fqdn, "1.1.1.1", DEL_ALL) == 0) { + forw_update_ok = 1; + } else { + error++; + } + + for (i = 0; i < nc_cnt; i++) { + net_cfg_t cfg; + if (smb_nic_get_byind(i, &cfg) == NULL) + break; + addr.s_addr = cfg.ip; + if (addr.s_addr == 0) + continue; + if (smb_nic_status(cfg.ifname, IFF_STANDBY) || + smb_nic_status(cfg.ifname, IFF_PRIVATE)) + continue; + + my_ip = (char *)strdup(inet_ntoa(addr)); + if (my_ip == NULL) { + error++; + continue; + } + + if (forw_update_ok) { + rc = dyndns_add_entry(UPDATE_FORW, fqdn, my_ip, + DDNS_TTL); + + if (rc == -1) + error++; + } + + rc = dyndns_remove_entry(UPDATE_REV, fqdn, my_ip, DEL_ALL); + if (rc == 0) { + rc = dyndns_add_entry(UPDATE_REV, fqdn, my_ip, + DDNS_TTL); + } + + if (rc == -1) + error++; + + (void) free(my_ip); + } + + return ((error == 0) ? 0 : -1); +} + +/* + * dyndns_clear_rev_zone + * Clear the rev zone records. Must be called to clear the OLD if list + * of down records prior to updating the list with new information. + * + * Parameters: + * None + * Returns: + * -1: some dynamic DNS updates errors + * 0: successful + */ +int +dyndns_clear_rev_zone(void) +{ + int i, error; + char fqdn[MAXHOSTNAMELEN]; + char *my_ip; + int nc_cnt; + struct in_addr addr; + int rc; + + if (!dyndns_enabled()) + return (-1); + + if (smb_getfqhostname(fqdn, MAXHOSTNAMELEN) != 0) + return (-1); + + nc_cnt = smb_nic_get_num(); + + error = 0; + + for (i = 0; i < nc_cnt; i++) { + net_cfg_t cfg; + if (smb_nic_get_byind(i, &cfg) == NULL) + break; + addr.s_addr = cfg.ip; + if (addr.s_addr == 0) + continue; + if (smb_nic_status(cfg.ifname, IFF_STANDBY) || + smb_nic_status(cfg.ifname, IFF_PRIVATE)) + continue; + + my_ip = (char *)strdup(inet_ntoa(addr)); + if (my_ip == NULL) { + error++; + continue; + } + + rc = dyndns_remove_entry(UPDATE_REV, fqdn, my_ip, DEL_ALL); + if (rc != 0) + error++; + + (void) free(my_ip); + } + + return ((error == 0) ? 0 : -1); +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.h b/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.h new file mode 100644 index 0000000000..7140eb4d59 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.h @@ -0,0 +1,205 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SMBSRV_DYNDNS_H +#define _SMBSRV_DYNDNS_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <smbsrv/libsmbns.h> + +/* + * Header section format: + * + * The header contains the following fields: + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ID | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * |QR| Opcode |AA|TC|RD|RA| Z | RCODE | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | QDCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ANCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | NSCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ARCOUNT | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * where: + * + * ID A 16 bit identifier assigned by the program that + * generates any kind of query. This identifier is copied + * the corresponding reply and can be used by the requester + * to match up replies to outstanding queries. + * + * QR A one bit field that specifies whether this message is a + * query (0), or a response (1). + * + * OPCODE A four bit field that specifies kind of query in this + * message. This value is set by the originator of a query + * and copied into the response. The values are: + * + * 0 a standard query (QUERY) + * + * 1 an inverse query (IQUERY) + * + * 2 a server status request (STATUS) + * + * 3-15 reserved for future use + * + * AA Authoritative Answer - this bit is valid in responses, + * and specifies that the responding name server is an + * authority for the domain name in question section. + * + * Note that the contents of the answer section may have + * multiple owner names because of aliases. The AA bit + * + * corresponds to the name which matches the query name, or + * the first owner name in the answer section. + * + * TC TrunCation - specifies that this message was truncated + * due to length greater than that permitted on the + * transmission channel. + * + * RD Recursion Desired - this bit may be set in a query and + * is copied into the response. If RD is set, it directs + * the name server to pursue the query recursively. + * Recursive query support is optional. + * + * RA Recursion Available - this be is set or cleared in a + * response, and denotes whether recursive query support is + * available in the name server. + * + * Z Reserved for future use. Must be zero in all queries + * and responses. + * + * RCODE Response code - this 4 bit field is set as part of + * responses. The values have the following + * interpretation: + * + * 0 No error condition + * + * 1 Format error - The name server was + * unable to interpret the query. + * + * 2 Server failure - The name server was + * unable to process this query due to a + * problem with the name server. + * + * 3 Name Error - Meaningful only for + * responses from an authoritative name + * server, this code signifies that the + * domain name referenced in the query does + * not exist. + * + * 4 Not Implemented - The name server does + * not support the requested kind of query. + * + * 5 Refused - The name server refuses to + * perform the specified operation for + * policy reasons. For example, a name + * server may not wish to provide the + * information to the particular requester, + * or a name server may not wish to perform + * a particular operation (e.g., zone + * + * transfer) for particular data. + * + * 6-15 Reserved for future use. + * + * QDCOUNT an unsigned 16 bit integer specifying the number of + * entries in the question section. + * + * ANCOUNT an unsigned 16 bit integer specifying the number of + * resource records in the answer section. + * + * NSCOUNT an unsigned 16 bit integer specifying the number of name + * server resource records in the authority records + * section. + * + * ARCOUNT an unsigned 16 bit integer specifying the number of + * resource records in the additional records section. + */ + +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Other definitions: */ +#define REQ_QUERY 1 /* DNS query request */ +#define REQ_UPDATE 0 /* DNS update request */ +#define UPDATE_FORW 1 /* Update forward lookup zone */ +#define UPDATE_REV 0 /* Update reverse lookup zone */ +#define UPDATE_ADD 1 /* Update add request */ +#define UPDATE_DEL 0 /* Update remove request */ +#define MODE_GSS_API 3 /* Key negotiation mode */ + +/* Max buffer size for send and receive buffer */ +#define MAX_BUF_SIZE 2000 +#define MAX_RETRIES 3 /* Max number of send retries if no response */ +#define TSIG_SIGNED 1 /* TSIG contains signed data */ +#define TSIG_UNSIGNED 0 /* TSIG does not conain signed data */ +#define DNS_CHECK 1 /* Check DNS for entry */ +#define DNS_NOCHECK 0 /* Don't check DNS for entry */ +#define MAX_TCP_SIZE 2000 /* max tcp DNS message size */ + +/* Delete 1 entry */ +#define DEL_ONE 1 +/* Delete all entries of the same resource name */ +#define DEL_ALL 0 + +#define DNSF_RECUR_SUPP 0x80 /* Server can do recursive queries */ +#define DNSF_RECUR_QRY 0x100 /* Query is recursive */ + +#define BUFLEN_TCP(x, y) (MAX_TCP_SIZE-(x-y)) +#define BUFLEN_UDP(x, y) (NS_PACKETSZ-(x-y)) + +extern char *dyndns_get_nshort(char *, uint16_t *); +extern char *dyndns_get_int(char *, int *); +extern int dyndns_build_header(char **, int, uint16_t, int, + uint16_t, uint16_t, uint16_t, uint16_t, int); +extern int dyndns_build_quest_zone(char **, int, char *, int, int); +extern int dyndns_open_init_socket(int sock_type, unsigned long dest_addr, + int port); +extern int dyndns_udp_send_recv(int, char *, int, char *); +extern void dyndns_msg_err(int); + +/* + * DDNS_TTL is the time to live in DNS caches. Note that this + * does not affect the entry in the authoritative DNS database. + */ +#define DDNS_TTL 1200 + +#ifdef __cplusplus +} +#endif + +#endif /* _SMBSRV_DYNDNS_H */ diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c new file mode 100644 index 0000000000..c1a2de7913 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c @@ -0,0 +1,543 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Copyright 1990 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * + * Initialize a credentials cache. + */ +#include <kerberosv5/krb5.h> +#include <kerberosv5/com_err.h> +#include <string.h> +#include <stdio.h> +#include <time.h> +#include <netdb.h> +#include <syslog.h> +#include <locale.h> +#include <strings.h> +#include <sys/synch.h> +#include <gssapi/gssapi.h> + +#include <smbsrv/libsmbns.h> + +#include <smbns_krb.h> + +static int krb5_acquire_cred_kinit_main(); + +typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type; + +struct k_opts { + /* in seconds */ + krb5_deltat starttime; + krb5_deltat lifetime; + krb5_deltat rlife; + + int forwardable; + int proxiable; + int addresses; + + int not_forwardable; + int not_proxiable; + int no_addresses; + + int verbose; + + char *principal_name; + char *principal_passwd; + char *service_name; + char *keytab_name; + char *k5_cache_name; + char *k4_cache_name; + + action_type action; +}; + +struct k5_data { + krb5_context ctx; + krb5_ccache cc; + krb5_principal me; + char *name; +}; + +static int +k5_begin(struct k_opts *opts, struct k5_data *k5) +{ + int code; + code = krb5_init_context(&k5->ctx); + if (code) { + return (code); + } + + if ((code = krb5_cc_default(k5->ctx, &k5->cc))) { + return (code); + } + + /* Use specified name */ + if ((code = krb5_parse_name(k5->ctx, opts->principal_name, &k5->me))) { + return (code); + } + + code = krb5_unparse_name(k5->ctx, k5->me, &k5->name); + if (code) { + return (code); + } + opts->principal_name = k5->name; + + return (0); +} + +static void +k5_end(struct k5_data *k5) +{ + if (k5->name) + krb5_free_unparsed_name(k5->ctx, k5->name); + if (k5->me) + krb5_free_principal(k5->ctx, k5->me); + if (k5->cc) + krb5_cc_close(k5->ctx, k5->cc); + if (k5->ctx) + krb5_free_context(k5->ctx); + (void) memset(k5, 0, sizeof (*k5)); +} + +static int +k5_kinit(struct k_opts *opts, struct k5_data *k5) +{ + int notix = 1; + krb5_keytab keytab = 0; + krb5_creds my_creds; + krb5_error_code code = 0; + krb5_get_init_creds_opt options; + const char *errmsg; + + krb5_get_init_creds_opt_init(&options); + (void) memset(&my_creds, 0, sizeof (my_creds)); + + /* + * From this point on, we can goto cleanup because my_creds is + * initialized. + */ + if (opts->lifetime) + krb5_get_init_creds_opt_set_tkt_life(&options, opts->lifetime); + if (opts->rlife) + krb5_get_init_creds_opt_set_renew_life(&options, opts->rlife); + if (opts->forwardable) + krb5_get_init_creds_opt_set_forwardable(&options, 1); + if (opts->not_forwardable) + krb5_get_init_creds_opt_set_forwardable(&options, 0); + if (opts->proxiable) + krb5_get_init_creds_opt_set_proxiable(&options, 1); + if (opts->not_proxiable) + krb5_get_init_creds_opt_set_proxiable(&options, 0); + if (opts->addresses) { + krb5_address **addresses = NULL; + code = krb5_os_localaddr(k5->ctx, &addresses); + if (code != 0) { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " + "getting local addresses (%s)"), errmsg); + goto cleanup; + } + krb5_get_init_creds_opt_set_address_list(&options, addresses); + } + if (opts->no_addresses) + krb5_get_init_creds_opt_set_address_list(&options, NULL); + + if ((opts->action == INIT_KT) && opts->keytab_name) { + code = krb5_kt_resolve(k5->ctx, opts->keytab_name, &keytab); + if (code != 0) { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " + "resolving keytab %s (%s)"), errmsg, + opts->keytab_name); + goto cleanup; + } + } + + switch (opts->action) { + case INIT_PW: + code = krb5_get_init_creds_password(k5->ctx, &my_creds, k5->me, + opts->principal_passwd, NULL, 0, opts->starttime, + opts->service_name, &options); + break; + case INIT_KT: + code = krb5_get_init_creds_keytab(k5->ctx, &my_creds, k5->me, + keytab, opts->starttime, opts->service_name, &options); + break; + case VALIDATE: + code = krb5_get_validated_creds(k5->ctx, &my_creds, k5->me, + k5->cc, opts->service_name); + break; + case RENEW: + code = krb5_get_renewed_creds(k5->ctx, &my_creds, k5->me, + k5->cc, opts->service_name); + break; + } + + if (code) { + char *doing = 0; + switch (opts->action) { + case INIT_PW: + case INIT_KT: + doing = dgettext(TEXT_DOMAIN, "k5_kinit: " + "getting initial credentials"); + break; + case VALIDATE: + doing = dgettext(TEXT_DOMAIN, "k5_kinit: " + "validating credentials"); + break; + case RENEW: + doing = dgettext(TEXT_DOMAIN, "k5_kinit: " + "renewing credentials"); + break; + } + + /* + * If got code == KRB5_AP_ERR_V4_REPLY && got_k4, we should + * let the user know that maybe he/she wants -4. + */ + if (code == KRB5KRB_AP_ERR_V4_REPLY) { + syslog(LOG_ERR, "%s\n" + "The KDC doesn't support v5. " + "You may want the -4 option in the future", doing); + return (1); + } else if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) { + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s " + "(Password incorrect)"), doing); + } else { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s (%s)"), + doing, errmsg); + } + goto cleanup; + } + + if (!opts->lifetime) { + /* We need to figure out what lifetime to use for Kerberos 4. */ + opts->lifetime = my_creds.times.endtime - + my_creds.times.authtime; + } + + code = krb5_cc_initialize(k5->ctx, k5->cc, k5->me); + if (code) { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " + "initializing cache %s (%s)"), + opts->k5_cache_name?opts->k5_cache_name:"", errmsg); + goto cleanup; + } + + code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds); + if (code) { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " + "storing credentials (%s)"), errmsg); + goto cleanup; + } + + notix = 0; + + cleanup: + if (my_creds.client == k5->me) { + my_creds.client = 0; + } + krb5_free_cred_contents(k5->ctx, &my_creds); + if (keytab) + krb5_kt_close(k5->ctx, keytab); + return (notix?0:1); +} + +int +smb_kinit(char *user, char *passwd) +{ + struct k_opts opts; + struct k5_data k5; + int authed_k5 = 0; + + (void) memset(&opts, 0, sizeof (opts)); + opts.action = INIT_PW; + opts.principal_name = strdup(user); + opts.principal_passwd = strdup(passwd); + + (void) memset(&k5, 0, sizeof (k5)); + + if (k5_begin(&opts, &k5) != 0) { + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "smb_kinit: " + "NOT Authenticated to Kerberos v5 k5_begin failed\n")); + return (0); + } + + authed_k5 = k5_kinit(&opts, &k5); + if (authed_k5) { + syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: " + "Authenticated to Kerberos v5\n")); + } else { + syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: " + "NOT Authenticated to Kerberos v5\n")); + } + + k5_end(&k5); + + return (authed_k5); +} + +/* + * krb5_display_stat + * Display error message for GSS-API routines. + * Parameters: + * maj : GSS major status + * min : GSS minor status + * caller_mod: module name that calls this routine so that the module name + * can be displayed with the error messages + * Returns: + * None + */ +static void +krb5_display_stat(OM_uint32 maj, OM_uint32 min, char *caller_mod) +{ + gss_buffer_desc msg; + OM_uint32 msg_ctx = 0; + OM_uint32 min2; + (void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, + &msg_ctx, &msg); + syslog(LOG_ERR, "%s: major status error: %s\n", + caller_mod, (char *)msg.value); + (void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, + &msg_ctx, &msg); + syslog(LOG_ERR, "%s: minor status error: %s\n", + caller_mod, (char *)msg.value); +} + +/* + * krb5_acquire_cred_kinit + * + * Wrapper for krb5_acquire_cred_kinit_main with mutex to protect credential + * cache file when calling krb5_acquire_cred or kinit. + */ + +int +krb5_acquire_cred_kinit(char *user, char *pwd, gss_cred_id_t *cred_handle, + gss_OID *oid, int *kinit_retry, char *caller_mod) +{ + int ret; + + ret = krb5_acquire_cred_kinit_main(user, pwd, + cred_handle, oid, kinit_retry, caller_mod); + return (ret); +} + +/* + * krb5_acquire_cred_kinit_main + * + * This routine is called both by ADS and Dyn DNS modules to get a handle to + * administrative user's credential stored locally on the system. The + * credential is the TGT. If the attempt at getting handle fails then a second + * attempt will be made after getting a new TGT. + * + * Paramters: + * user : username to retrieve a handle to its credential + * pwd : password of username in case obtaining a new TGT is needed + * kinit_retry: if 0 then a second attempt will be made to get handle to the + * credential if the first attempt fails + * caller_mod : name of module that call this routine so that the module name + * can be included with error messages + * Returns: + * cred_handle: handle to the administrative user's credential (TGT) + * oid : contains Kerberos 5 object identifier + * kinit_retry: A 1 indicates that a second attempt has been made to get + * handle to the credential and no further attempts can be made + * -1 : error + * 0 : success + */ +static int +krb5_acquire_cred_kinit_main(char *user, char *pwd, gss_cred_id_t *cred_handle, + gss_OID *oid, int *kinit_retry, char *caller_mod) +{ + OM_uint32 maj, min; + gss_name_t desired_name; + gss_OID_set desired_mechs; + gss_buffer_desc oidstr, name_buf; + char str[50], user_name[50]; + + acquire_cred: + + /* Object Identifier for Kerberos 5 */ + (void) strcpy(str, "{ 1 2 840 113554 1 2 2 }"); + oidstr.value = str; + oidstr.length = strlen(str); + if ((maj = gss_str_to_oid(&min, &oidstr, oid)) != GSS_S_COMPLETE) { + krb5_display_stat(maj, min, caller_mod); + return (-1); + } + if ((maj = gss_create_empty_oid_set(&min, &desired_mechs)) + != GSS_S_COMPLETE) { + krb5_display_stat(maj, min, caller_mod); + (void) gss_release_oid(&min, oid); + return (-1); + } + if ((maj = gss_add_oid_set_member(&min, *oid, &desired_mechs)) + != GSS_S_COMPLETE) { + krb5_display_stat(maj, min, caller_mod); + (void) gss_release_oid(&min, oid); + (void) gss_release_oid_set(&min, &desired_mechs); + return (-1); + } + + (void) strcpy(user_name, user); + name_buf.value = user_name; + name_buf.length = strlen(user_name)+1; + if ((maj = gss_import_name(&min, &name_buf, GSS_C_NT_USER_NAME, + &desired_name)) != GSS_S_COMPLETE) { + krb5_display_stat(maj, min, caller_mod); + (void) gss_release_oid(&min, oid); + (void) gss_release_oid_set(&min, &desired_mechs); + return (-1); + } + + if ((maj = gss_acquire_cred(&min, desired_name, 0, desired_mechs, + GSS_C_INITIATE, cred_handle, NULL, NULL)) != GSS_S_COMPLETE) { + if (!*kinit_retry) { + (void) gss_release_oid(&min, oid); + (void) gss_release_oid_set(&min, &desired_mechs); + (void) gss_release_name(&min, &desired_name); + syslog(LOG_ERR, "%s: Retry kinit to " + "acquire credential.\n", caller_mod); + (void) smb_kinit(user, pwd); + *kinit_retry = 1; + goto acquire_cred; + } else { + krb5_display_stat(maj, min, caller_mod); + (void) gss_release_oid(&min, oid); + (void) gss_release_oid_set(&min, &desired_mechs); + (void) gss_release_name(&min, &desired_name); + return (-1); + } + } + (void) gss_release_oid_set(&min, &desired_mechs); + (void) gss_release_name(&min, &desired_name); + + return (0); +} + +/* + * krb5_establish_sec_ctx_kinit + * + * This routine is called by both the ADS and Dyn DNS modules to establish a + * security context before ADS or Dyn DNS updates are allowed. If establishing + * a security context fails for any reason, a second attempt will be made after + * a new TGT is obtained. This routine is called many time as needed until + * a security context is established. + * + * The resources use for the security context must be released if security + * context establishment process fails. + * Parameters: + * user : user used in establishing a security context for. Is used for + * obtaining a new TGT for a second attempt at establishing + * security context + * pwd : password of above user + * cred_handle: a handle to the user credential (TGT) stored locally + * gss_context: initially set to GSS_C_NO_CONTEXT but will contain a handle + * to a security context + * target_name: contains service name to establish a security context with, + * ie ldap or dns + * gss_flags : flags used in establishing security context + * inputptr : initially set to GSS_C_NO_BUFFER but will be token data + * received from service's server to be processed to generate + * further token to be sent back to service's server during + * security context establishment + * kinit_retry: if 0 then a second attempt will be made to get handle to the + * credential if the first attempt fails + * caller_mod : name of module that call this routine so that the module name + * can be included with error messages + * Returns: + * gss_context : a handle to a security context + * out_tok : token data to be sent to service's server to establish + * security context + * ret_flags : return flags + * time_rec : valid time for security context, not currently used + * kinit_retry : A 1 indicates that a second attempt has been made to get + * handle to the credential and no further attempts can be + * made + * do_acquire_cred: A 1 indicates that a new handle to the local credential + * is needed for second attempt at security context + * establishment + * maj : major status code used if determining is security context + * establishment is successful + */ +int +krb5_establish_sec_ctx_kinit(char *user, char *pwd, + gss_cred_id_t cred_handle, gss_ctx_id_t *gss_context, + gss_name_t target_name, gss_OID oid, int gss_flags, + gss_buffer_desc *inputptr, gss_buffer_desc* out_tok, + OM_uint32 *ret_flags, OM_uint32 *time_rec, + int *kinit_retry, int *do_acquire_cred, + OM_uint32 *maj, char *caller_mod) +{ + OM_uint32 min; + + *maj = gss_init_sec_context(&min, cred_handle, gss_context, + target_name, oid, gss_flags, 0, NULL, inputptr, NULL, + out_tok, ret_flags, time_rec); + if (*maj != GSS_S_COMPLETE && *maj != GSS_S_CONTINUE_NEEDED) { + if (*gss_context != NULL) + (void) gss_delete_sec_context(&min, gss_context, NULL); + + if (!*kinit_retry) { + syslog(LOG_ERR, "%s: Retry kinit to establish " + "security context.\n", caller_mod); + (void) smb_kinit(user, pwd); + *kinit_retry = 1; + *do_acquire_cred = 1; + return (-1); + } else { + krb5_display_stat(*maj, min, caller_mod); + return (-1); + } + } + return (0); +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.h b/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.h new file mode 100644 index 0000000000..22028fcdb5 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.h @@ -0,0 +1,64 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SMBSRV_SMB_KRB5_H +#define _SMBSRV_SMB_KRB5_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <gssapi/gssapi.h> +#include <kerberosv5/krb5.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern gss_OID gss_nt_user_name; +extern gss_OID gss_nt_machine_uid_name; +extern gss_OID gss_nt_string_uid_name; +extern gss_OID gss_nt_service_name; +extern gss_OID gss_nt_exported_name; +extern gss_OID gss_nt_service_name_v2; + +int krb5_acquire_cred_kinit(char *, char *, gss_cred_id_t *, + gss_OID *, int *, char *); +int krb5_establish_sec_ctx_kinit(char *, char *, gss_cred_id_t, + gss_ctx_id_t *, gss_name_t, gss_OID, int, gss_buffer_desc *, + gss_buffer_desc *, OM_uint32 *, OM_uint32 *, int *, + int *, OM_uint32 *, char *); +int smb_krb5_ctx_init(krb5_context *ctx); +void smb_krb5_ctx_fini(krb5_context ctx); +int smb_krb5_get_principal(krb5_context ctx, char *princ_str, + krb5_principal *princ); +int smb_krb5_setpwd(krb5_context ctx, krb5_principal princ, char *passwd); +int smb_krb5_write_keytab(krb5_context ctx, krb5_principal princ, + char *fname, krb5_kvno kvno, char *passwd, krb5_enctype *enctypes, + int enctype_count); + +#ifdef __cplusplus +} +#endif + +#endif /* _SMBSRV_SMB_KRB5_H */ diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_ksetpwd.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_ksetpwd.c new file mode 100644 index 0000000000..a6c99799d1 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_ksetpwd.c @@ -0,0 +1,242 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <syslog.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <kerberosv5/krb5.h> + +static int smb_krb5_ktadd(krb5_context ctx, krb5_keytab kt, + const krb5_principal princ, krb5_enctype enctype, krb5_kvno kvno, + const char *pw); + +/* + * smb_krb5_ctx_init + * + * Initialize the kerberos context. + * Return 0 on success. Otherwise, return -1. + */ +int +smb_krb5_ctx_init(krb5_context *ctx) +{ + if (krb5_init_context(ctx) != 0) + return (-1); + + return (0); +} + +/* + * smb_krb5_get_principal + * + * Setup the krb5_principal given the host principal in string format. + * Return 0 on success. Otherwise, return -1. + */ +int +smb_krb5_get_principal(krb5_context ctx, char *princ_str, krb5_principal *princ) +{ + if (krb5_parse_name(ctx, princ_str, princ) != 0) + return (-1); + + return (0); +} + +/* + * smb_krb5_ctx_fini + * + * Free the kerberos context. + */ +void +smb_krb5_ctx_fini(krb5_context ctx) +{ + krb5_free_context(ctx); +} + +/* + * smb_ksetpw + * + * Set the workstation trust account password. + * Returns 0 on success. Otherwise, returns non-zero value. + */ +int +smb_krb5_setpwd(krb5_context ctx, krb5_principal princ, char *passwd) +{ + krb5_error_code code; + krb5_ccache cc = NULL; + int result_code; + krb5_data result_code_string, result_string; + + (void) memset(&result_code_string, 0, sizeof (result_code_string)); + (void) memset(&result_string, 0, sizeof (result_string)); + + if ((code = krb5_cc_default(ctx, &cc)) != 0) { + syslog(LOG_ERR, "smb_krb5_setpwd: failed to find a ccache\n"); + return (-1); + } + + code = krb5_set_password_using_ccache(ctx, cc, passwd, princ, + &result_code, &result_code_string, &result_string); + + krb5_cc_close(ctx, cc); + + if (code != 0) + (void) syslog(LOG_ERR, + "smb_krb5_setpwd: Result: %.*s (%d) %.*s\n", + result_code == 0 ? + strlen("success") : result_code_string.length, + result_code == 0 ? "success" : result_code_string.data, + result_code, result_string.length, result_string.data); + return (code); +} + +/* + * smb_krb5_write_keytab + * + * Write all the Kerberos keys to the keytab file. + * Returns 0 on success. Otherwise, returns -1. + */ +int +smb_krb5_write_keytab(krb5_context ctx, krb5_principal princ, char *fname, + krb5_kvno kvno, char *passwd, krb5_enctype *enctypes, int enctype_count) +{ + krb5_keytab kt = NULL; + char *ktname; + int i, len; + int rc = 0; + struct stat fstat; + + if (stat(fname, &fstat) == 0) { + if (remove(fname) != 0) { + syslog(LOG_ERR, "smb_krb5_write_keytab: cannot remove" + " existing keytab"); + return (-1); + } + } + + len = snprintf(NULL, 0, "WRFILE:%s", fname) + 1; + if ((ktname = malloc(len)) == NULL) { + syslog(LOG_ERR, "smb_krb5_write_keytab: resource shortage"); + return (-1); + } + + (void) snprintf(ktname, len, "WRFILE:%s", fname); + + if (krb5_kt_resolve(ctx, ktname, &kt) != 0) { + syslog(LOG_ERR, "smb_krb5_write_keytab: failed to open/create " + "keytab %s\n", fname); + free(ktname); + return (-1); + } + + free(ktname); + + for (i = 0; i < enctype_count; i++) { + if (smb_krb5_ktadd(ctx, kt, princ, enctypes[i], kvno, passwd) + != 0) { + rc = -1; + break; + } + + } + + if (kt != NULL) + krb5_kt_close(ctx, kt); + + return (rc); +} + +/* + * smb_krb5_ktadd + * + * Add a Keberos key to the keytab file. + * Returns 0 on success. Otherwise, returns -1. + */ +static int +smb_krb5_ktadd(krb5_context ctx, krb5_keytab kt, const krb5_principal princ, + krb5_enctype enctype, krb5_kvno kvno, const char *pw) +{ + krb5_keytab_entry *entry; + krb5_data password, salt; + krb5_keyblock key; + krb5_error_code code; + char buf[100]; + int rc = 0; + + if ((code = krb5_enctype_to_string(enctype, buf, sizeof (buf)))) { + syslog(LOG_ERR, "smb_krb5_ktadd[%d]: unknown enctype", + enctype); + return (-1); + } + + if ((entry = (krb5_keytab_entry *) malloc(sizeof (*entry))) == NULL) { + syslog(LOG_ERR, "smb_krb5_ktadd[%d]: resource shortage", + enctype); + return (-1); + } + + (void) memset((char *)entry, 0, sizeof (*entry)); + + password.length = strlen(pw); + password.data = (char *)pw; + + if ((code = krb5_principal2salt(ctx, princ, &salt)) != 0) { + syslog(LOG_ERR, "smb_krb5_ktadd[%d]: failed to compute salt", + enctype); + free(entry); + return (-1); + } + + code = krb5_c_string_to_key(ctx, enctype, &password, &salt, &key); + krb5_xfree(salt.data); + if (code != 0) { + syslog(LOG_ERR, "smb_krb5_ktadd[%d]: failed to generate key", + enctype); + free(entry); + return (-1); + } + + (void) memcpy(&entry->key, &key, sizeof (krb5_keyblock)); + entry->vno = kvno; + entry->principal = princ; + + if ((code = krb5_kt_add_entry(ctx, kt, entry)) != 0) { + syslog(LOG_ERR, "smb_krb5_ktadd[%d] failed to add entry to " + "keytab (%d)", enctype, code); + rc = -1; + } + + free(entry); + return (rc); +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios.c new file mode 100644 index 0000000000..7b165e3b44 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios.c @@ -0,0 +1,300 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Main startup code for SMB/NETBIOS and some utility routines + * for the NETBIOS layer. + */ + +#include <synch.h> +#include <unistd.h> +#include <syslog.h> +#include <string.h> +#include <strings.h> +#include <sys/socket.h> + +#include <smbns_netbios.h> + +netbios_status_t nb_status; + +static pthread_t smb_nbns_thr; /* name service */ +static pthread_t smb_nbds_thr; /* dgram service */ +static pthread_t smb_nbts_thr; /* timer */ +static pthread_t smb_nbbs_thr; /* browser */ + +static void *smb_netbios_timer(void *); + +void +smb_netbios_chg_status(uint32_t status, int set) +{ + (void) mutex_lock(&nb_status.mtx); + if (set) + nb_status.state |= status; + else + nb_status.state &= ~status; + (void) cond_broadcast(&nb_status.cv); + (void) mutex_unlock(&nb_status.mtx); +} + +void +smb_netbios_shutdown(void) +{ + smb_netbios_chg_status(NETBIOS_SHUTTING_DOWN, 1); + + (void) pthread_join(smb_nbts_thr, 0); + (void) pthread_join(smb_nbbs_thr, 0); + (void) pthread_join(smb_nbns_thr, 0); + (void) pthread_join(smb_nbds_thr, 0); + + nb_status.state = NETBIOS_SHUT_DOWN; +} + +void +smb_netbios_start() +{ + int rc; + mutex_t *mp; + cond_t *cvp; + + /* Startup Netbios named; port 137 */ + rc = pthread_create(&smb_nbns_thr, 0, + smb_netbios_name_service_daemon, 0); + if (rc) + return; + + mp = &nb_status.mtx; + cvp = &nb_status.cv; + + (void) mutex_lock(mp); + + while (!(nb_status.state & (NETBIOS_NAME_SVC_RUNNING | + NETBIOS_NAME_SVC_FAILED))) { + (void) cond_wait(cvp, mp); + } + + if (nb_status.state & NETBIOS_NAME_SVC_FAILED) { + (void) mutex_unlock(mp); + (void) fprintf(stderr, + "smbd: Netbios Name service startup failed!"); + smb_netbios_shutdown(); + return; + } + (void) mutex_unlock(mp); + + (void) fprintf(stderr, "smbd: Netbios Name service started."); + smb_netbios_name_config(); + + /* Startup Netbios datagram service; port 138 */ + rc = pthread_create(&smb_nbds_thr, 0, + smb_netbios_datagram_service_daemon, 0); + if (rc == 0) { + (void) mutex_lock(mp); + while (!(nb_status.state & (NETBIOS_DATAGRAM_SVC_RUNNING | + NETBIOS_DATAGRAM_SVC_FAILED))) { + (void) cond_wait(cvp, mp); + } + + if (nb_status.state & NETBIOS_DATAGRAM_SVC_FAILED) { + (void) mutex_unlock(mp); + (void) fprintf(stderr, "smbd: Netbios Datagram service " + "startup failed!"); + smb_netbios_shutdown(); + return; + } + (void) mutex_unlock(mp); + } else { + smb_netbios_shutdown(); + return; + } + + (void) fprintf(stderr, "smbd: Netbios Datagram service started."); + + /* Startup Netbios browser service */ + rc = pthread_create(&smb_nbbs_thr, 0, smb_browser_daemon, 0); + if (rc) { + smb_netbios_shutdown(); + return; + } + + (void) fprintf(stderr, "smbd: Netbios Browser client started."); + + /* Startup Our internal, 1 second resolution, timer */ + rc = pthread_create(&smb_nbts_thr, 0, smb_netbios_timer, 0); + if (rc == 0) { + (void) mutex_lock(mp); + while (!(nb_status.state & (NETBIOS_TIMER_RUNNING | + NETBIOS_TIMER_FAILED))) { + (void) cond_wait(cvp, mp); + } + + if (nb_status.state & NETBIOS_TIMER_FAILED) { + (void) mutex_unlock(mp); + smb_netbios_shutdown(); + return; + } + (void) mutex_unlock(mp); + } else { + smb_netbios_shutdown(); + return; + } + + (void) fprintf(stderr, "smbd: Netbios Timer service started."); +} + +/*ARGSUSED*/ +static void * +smb_netbios_timer(void *arg) +{ + static unsigned int ticks; + + smb_netbios_chg_status(NETBIOS_TIMER_RUNNING, 1); + + while ((nb_status.state & NETBIOS_SHUTTING_DOWN) == 0) { + (void) sleep(1); + + if (nb_status.state & NETBIOS_DATAGRAM_SVC_RUNNING) + smb_netbios_datagram_tick(); + else + break; + + if (nb_status.state & NETBIOS_NAME_SVC_RUNNING) { + smb_netbios_name_tick(); + + /* every 10 minutes */ + if ((ticks % 600) == 0) + smb_netbios_cache_clean(); + } + else + break; + } + + nb_status.state &= ~NETBIOS_TIMER_RUNNING; + if ((nb_status.state & NETBIOS_SHUTTING_DOWN) == 0) { + /* either name or datagram service has failed */ + smb_netbios_shutdown(); + } + + return (0); +} + +int +smb_first_level_name_encode(struct name_entry *name, + unsigned char *out, int max_out) +{ + return (netbios_first_level_name_encode(name->name, name->scope, + out, max_out)); +} + +int +smb_first_level_name_decode(unsigned char *in, struct name_entry *name) +{ + return (netbios_first_level_name_decode((char *)in, (char *)name->name, + (char *)name->scope)); +} + +/* + * smb_encode_netbios_name + * + * Set up the name and scope fields in the destination name_entry structure. + * The name is padded with spaces to 15 bytes. The suffix is copied into the + * last byte, i.e. "netbiosname <suffix>". The scope is copied and folded + * to uppercase. + */ +void +smb_encode_netbios_name(unsigned char *name, char suffix, unsigned char *scope, + struct name_entry *dest) +{ + char tmp_name[NETBIOS_NAME_SZ]; + mts_wchar_t wtmp_name[NETBIOS_NAME_SZ]; + unsigned int cpid; + int len; + size_t rc; + + len = 0; + rc = mts_mbstowcs(wtmp_name, (const char *)name, NETBIOS_NAME_SZ); + + if (rc != (size_t)-1) { + wtmp_name[NETBIOS_NAME_SZ - 1] = 0; + cpid = oem_get_smb_cpid(); + rc = unicodestooems(tmp_name, wtmp_name, NETBIOS_NAME_SZ, cpid); + if (rc > 0) + len = strlen(tmp_name); + } + + (void) memset(dest->name, ' ', NETBIOS_NAME_SZ - 1); + if (len) { + (void) utf8_strupr(tmp_name); + (void) memcpy(dest->name, tmp_name, len); + } + dest->name[NETBIOS_NAME_SZ - 1] = suffix; + + if (scope == NULL) { + smb_config_rdlock(); + (void) strlcpy((char *)dest->scope, + smb_config_getstr(SMB_CI_NBSCOPE), NETBIOS_DOMAIN_NAME_MAX); + smb_config_unlock(); + } else { + (void) strlcpy((char *)dest->scope, (const char *)scope, + NETBIOS_DOMAIN_NAME_MAX); + } + (void) utf8_strupr((char *)dest->scope); +} + +void +smb_init_name_struct(unsigned char *name, char suffix, unsigned char *scope, + uint32_t ipaddr, unsigned short port, uint32_t attr, + uint32_t addr_attr, struct name_entry *dest) +{ + bzero(dest, sizeof (struct name_entry)); + smb_encode_netbios_name(name, suffix, scope, dest); + + switch (smb_node_type) { + case 'H': + dest->attributes = attr | NAME_ATTR_OWNER_TYPE_HNODE; + break; + case 'M': + dest->attributes = attr | NAME_ATTR_OWNER_TYPE_MNODE; + break; + case 'P': + dest->attributes = attr | NAME_ATTR_OWNER_TYPE_PNODE; + break; + case 'B': + default: + dest->attributes = attr | NAME_ATTR_OWNER_TYPE_BNODE; + break; + } + + dest->addr_list.refresh_ttl = dest->addr_list.ttl = + TO_SECONDS(DEFAULT_TTL); + + dest->addr_list.sin.sin_family = AF_INET; + dest->addr_list.sinlen = sizeof (dest->addr_list.sin); + dest->addr_list.sin.sin_addr.s_addr = ipaddr; + dest->addr_list.sin.sin_port = port; + dest->addr_list.attributes = addr_attr; + dest->addr_list.forw = dest->addr_list.back = &dest->addr_list; +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios.h b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios.h new file mode 100644 index 0000000000..54ac5cf2ab --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios.h @@ -0,0 +1,1617 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SMB_NETBIOS_H_ +#define _SMB_NETBIOS_H_ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * 4.2. NAME SERVICE PACKETS + * + * 4.2.1. GENERAL FORMAT OF NAME SERVICE PACKETS + * + * The NetBIOS Name Service packets follow the packet structure defined + * in the Domain Name Service (DNS) RFC 883 [7 (pg 26-31)]. The + * structures are compatible with the existing DNS packet formats, + * however, additional types and codes have been added to work with + * NetBIOS. + * + * If Name Service packets are sent over a TCP connection they are + * preceded by a 16 bit unsigned integer representing the length of the + * Name Service packet. + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + ------ ------- + + * | HEADER | + * + ------ ------- + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION ENTRIES / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / ANSWER RESOURCE RECORDS / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / AUTHORITY RESOURCE RECORDS / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / ADDITIONAL RESOURCE RECORDS / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* + * 4.2.1.1 HEADER + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID | OPCODE | NM_FLAGS | RCODE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | QDCOUNT | ANCOUNT | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NSCOUNT | ARCOUNT | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Field Description + * + * NAME_TRN_ID Transaction ID for Name Service Transaction. + * Requester places a unique value for each active + * transaction. Responder puts NAME_TRN_ID value + * from request packet in response packet. + * + * OPCODE Packet type code, see table below. + * + * NM_FLAGS Flags for operation, see table below. + * + * RCODE Result codes of request. Table of RCODE values + * for each response packet below. + * + * QDCOUNT Unsigned 16 bit integer specifying the number of + * entries in the question section of a Name + * Service packet. Always zero (0) for responses. + * Must be non-zero for all NetBIOS Name requests. + * + * ANCOUNT Unsigned 16 bit integer specifying the number of + * resource records in the answer section of a Name + * Service packet. + * + * NSCOUNT Unsigned 16 bit integer specifying the number of + * resource records in the authority section of a + * Name Service packet. + * + * ARCOUNT Unsigned 16 bit integer specifying the number of + * resource records in the additional records + * section of a Name Service packet. + */ + + +/* + * The OPCODE field is defined as: + * + * 0 1 2 3 4 + * +---+---+---+---+---+ + * | R | OPCODE | + * +---+---+---+---+---+ + * + * Symbol Bit(s) Description + * + * OPCODE 1-4 Operation specifier: + * 0 = query + * 5 = registration + * 6 = release + * 7 = WACK + * 8 = refresh + * + * R 0 RESPONSE flag: + * if bit == 0 then request packet + * if bit == 1 then response packet. + */ + +/* + * The NM_FLAGS field is defined as: + * + * + * 0 1 2 3 4 5 6 + * +---+---+---+---+---+---+---+ + * |AA |TC |RD |RA | 0 | 0 | B | + * +---+---+---+---+---+---+---+ + * + * Symbol Bit(s) Description + * + * B 6 Broadcast Flag. + * = 1: packet was broadcast or multicast + * = 0: unicast + * + * RA 3 Recursion Available Flag. + * + * Only valid in responses from a NetBIOS Name + * Server -- must be zero in all other + * responses. + * + * If one (1) then the NBNS supports recursive + * query, registration, and release. + * + * If zero (0) then the end-node must iterate + * for query and challenge for registration. + * + * RD 2 Recursion Desired Flag. + * + * May only be set on a request to a NetBIOS + * Name Server. + * + * The NBNS will copy its state into the + * response packet. + * + * If one (1) the NBNS will iterate on the + * query, registration, or release. + * + * TC 1 Truncation Flag. + * + * Set if this message was truncated because the + * datagram carrying it would be greater than + * 576 bytes in length. Use TCP to get the + * information from the NetBIOS Name Server. + * + * AA 0 Authoritative Answer flag. + * + * Must be zero (0) if R flag of OPCODE is zero + * (0). + * + * If R flag is one (1) then if AA is one (1) + * then the node responding is an authority for + * the domain name. + * + * End nodes responding to queries always set + * this bit in responses. + */ + +/* + * 4.2.1.2 QUESTION SECTION + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | QUESTION_TYPE | QUESTION_CLASS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Field Description + * + * QUESTION_NAME The compressed name representation of the + * NetBIOS name for the request. + * + * QUESTION_TYPE The type of request. The values for this field + * are specified for each request. + * + * QUESTION_CLASS The class of the request. The values for this + * field are specified for each request. + * + * QUESTION_TYPE is defined as: + * + * Symbol Value Description: + * + * NB 0x0020 NetBIOS general Name Service Resource Record + * NBSTAT 0x0021 NetBIOS NODE STATUS Resource Record (See NODE + * STATUS REQUEST) + * + * QUESTION_CLASS is defined as: + * + * Symbol Value Description: + * + * IN 0x0001 Internet class + */ + +/* + * 4.2.1.3 RESOURCE RECORD + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RR_TYPE | RR_CLASS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RDLENGTH | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * / / + * / RDATA / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Field Description + * + * RR_NAME The compressed name representation of the + * NetBIOS name corresponding to this resource + * record. + * + * RR_TYPE Resource record type code + * + * RR_CLASS Resource record class code + * + * TTL The Time To Live of a the resource record's + * name. + * + * RDLENGTH Unsigned 16 bit integer that specifies the + * number of bytes in the RDATA field. + * + * RDATA RR_CLASS and RR_TYPE dependent field. Contains + * the resource information for the NetBIOS name. + * + * RESOURCE RECORD RR_TYPE field definitions: + * + * Symbol Value Description: + * + * A 0x0001 IP address Resource Record (See REDIRECT NAME + * QUERY RESPONSE) + * NS 0x0002 Name Server Resource Record (See REDIRECT + * NAME QUERY RESPONSE) + * NULL 0x000A NULL Resource Record (See WAIT FOR + * ACKNOWLEDGEMENT RESPONSE) + * NB 0x0020 NetBIOS general Name Service Resource Record + * (See NB_FLAGS and NB_ADDRESS, below) + * NBSTAT 0x0021 NetBIOS NODE STATUS Resource Record (See NODE + * STATUS RESPONSE) + * + * RESOURCE RECORD RR_CLASS field definitions: + * + * Symbol Value Description: + * + * IN 0x0001 Internet class + * + * NB_FLAGS field of the RESOURCE RECORD RDATA field for RR_TYPE of + * "NB": + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | G | ONT | RESERVED | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * Symbol Bit(s) Description: + * + * RESERVED 3-15 Reserved for future use. Must be zero (0). + * ONT 1,2 Owner Node Type: + * 00 = B node + * 01 = P node + * 10 = M node + * 11 = Reserved for future use + * For registration requests this is the + * claimant's type. + * For responses this is the actual owner's + * type. + * + * G 0 Group Name Flag. + * If one (1) then the RR_NAME is a GROUP + * NetBIOS name. + * If zero (0) then the RR_NAME is a UNIQUE + * NetBIOS name. + * + * The NB_ADDRESS field of the RESOURCE RECORD RDATA field for + * RR_TYPE of "NB" is the IP address of the name's owner. + */ + +/* + * 4.2.2. NAME REGISTRATION REQUEST + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x5 |0|0|1|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Since the RR_NAME is the same name as the QUESTION_NAME, the + * RR_NAME representation must use pointers to the QUESTION_NAME + * name's labels to guarantee the length of the datagram is less + * than the maximum 576 bytes. See section above on name formats + * and also page 31 and 32 of RFC 883, Domain Names - Implementation + * and Specification, for a complete description of compressed name + * label pointers. + */ + +/* + * 4.2.3 NAME OVERWRITE REQUEST & DEMAND + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x5 |0|0|0|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* + * 4.2.4 NAME REFRESH REQUEST + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x9 |0|0|0|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* + * 4.2.5 POSITIVE NAME REGISTRATION RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x5 |1|0|1|1|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* + * 4.2.6 NEGATIVE NAME REGISTRATION RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x5 |1|0|1|1|0 0|0| RCODE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * RCODE field values: + * + * Symbol Value Description: + * + * FMT_ERR 0x1 Format Error. Request was invalidly + * formatted. + * SRV_ERR 0x2 Server failure. Problem with NBNS, cannot + * process name. + * IMP_ERR 0x4 Unsupported request error. Allowable only + * for challenging NBNS when gets an Update type + * registration request. + * RFS_ERR 0x5 Refused error. For policy reasons server + * will not register this name from this host. + * ACT_ERR 0x6 Active error. Name is owned by another node. + * CFT_ERR 0x7 Name in conflict error. A UNIQUE name is + * owned by more than one node. + */ + +/* + * 4.2.7 END-NODE CHALLENGE REGISTRATION RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x5 |1|0|1|0|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* + * 4.2.8 NAME CONFLICT DEMAND + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x5 |1|0|1|1|0 0|0| 0x7 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x00000000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 |0|ONT|0| 0x000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x00000000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * This packet is identical to a NEGATIVE NAME REGISTRATION RESPONSE + * with RCODE = CFT_ERR. + */ + +/* + * 4.2.9 NAME RELEASE REQUEST & DEMAND + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x6 |0|0|0|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x00000000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Since the RR_NAME is the same name as the QUESTION_NAME, the + * RR_NAME representation must use label string pointers to the + * QUESTION_NAME labels to guarantee the length of the datagram is + * less than the maximum 576 bytes. This is the same condition as + * with the NAME REGISTRATION REQUEST. + */ + +/* + * 4.2.10 POSITIVE NAME RELEASE RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x6 |1|0|0|0|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* + * 4.2.11 NEGATIVE NAME RELEASE RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x6 |1|0|0|0|0 0|0| RCODE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * RCODE field values: + * + * Symbol Value Description: + * + * FMT_ERR 0x1 Format Error. Request was invalidly + * formatted. + * + * SRV_ERR 0x2 Server failure. Problem with NBNS, cannot + * process name. + * + * RFS_ERR 0x5 Refused error. For policy reasons server + * will not release this name from this host. + * + * ACT_ERR 0x6 Active error. Name is owned by another node. + * Only that node may release it. A NetBIOS + * Name Server can optionally allow a node to + * release a name it does not own. This would + * facilitate detection of inactive names for + * nodes that went down silently. + */ + +/* + * 4.2.12 NAME QUERY REQUEST + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x0 |0|0|1|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* + * 4.2.13 POSITIVE NAME QUERY RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x0 |1|T|1|?|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RDLENGTH | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | + * / ADDR_ENTRY ARRAY / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The ADDR_ENTRY ARRAY a sequence of zero or more ADDR_ENTRY + * records. Each ADDR_ENTRY record represents an owner of a name. + * For group names there may be multiple entries. However, the list + * may be incomplete due to packet size limitations. Bit 22, "T", + * will be set to indicate truncated data. + * + * Each ADDR_ENTRY has the following format: + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_FLAGS | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS (continued) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* + * 4.2.14 NEGATIVE NAME QUERY RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x0 |1|0|1|?|0 0|0| RCODE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NULL (0x000A) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x00000000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * RCODE field values: + * + * Symbol Value Description + * + * FMT_ERR 0x1 Format Error. Request was invalidly + * formatted. + * SRV_ERR 0x2 Server failure. Problem with NBNS, cannot + * process name. + * NAM_ERR 0x3 Name Error. The name requested does not + * exist. + * IMP_ERR 0x4 Unsupported request error. Allowable only + * for challenging NBNS when gets an Update type + * registration request. + * RFS_ERR 0x5 Refused error. For policy reasons server + * will not register this name from this host. + */ + +/* + * 4.2.15 REDIRECT NAME QUERY RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x0 |0|0|1|0|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NS (0x0002) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RDLENGTH | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + * | | + * / NSD_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | A (0x0001) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0004 | NSD_IP_ADDR | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NSD_IP_ADDR, continued | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * An end node responding to a NAME QUERY REQUEST always responds + * with the AA and RA bits set for both the NEGATIVE and POSITIVE + * NAME QUERY RESPONSE packets. An end node never sends a REDIRECT + * NAME QUERY RESPONSE packet. + * + * When the requestor receives the REDIRECT NAME QUERY RESPONSE it + * must reiterate the NAME QUERY REQUEST to the NBNS specified by + * the NSD_IP_ADDR field of the A type RESOURCE RECORD in the + * ADDITIONAL section of the response packet. This is an optional + * packet for the NBNS. + * + * The NSD_NAME and the RR_NAME in the ADDITIONAL section of the + * response packet are the same name. Space can be optimized if + * label string pointers are used in the RR_NAME which point to the + * labels in the NSD_NAME. + * + * The RR_NAME in the AUTHORITY section is the name of the domain + * the NBNS called by NSD_NAME has authority over. + */ + +/* + * 4.2.16 WAIT FOR ACKNOWLEDGEMENT (WACK) RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x7 |1|0|0|0|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NULL (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0002 | OPCODE | NM_FLAGS | 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The NAME_TRN_ID of the WACK RESPONSE packet is the same + * NAME_TRN_ID of the request that the NBNS is telling the requestor + * to wait longer to complete. The RR_NAME is the name from the + * request, if any. If no name is available from the request then + * it is a null name, single byte of zero. + * + * The TTL field of the ResourceRecord is the new time to wait, in + * seconds, for the request to complete. The RDATA field contains + * the OPCODE and NM_FLAGS of the request. + * + * A TTL value of 0 means that the NBNS can not estimate the time it + * may take to complete a response. + */ + +/* + * 4.2.17 NODE STATUS REQUEST + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x0 |0|0|0|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NBSTAT (0x0021) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +/* + * 4.2.18 NODE STATUS RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x0 |1|0|0|0|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NBSTAT (0x0021) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x00000000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RDLENGTH | NUM_NAMES | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + * | | + * + + + * / NODE_NAME ARRAY / + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * / STATISTICS / + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The NODE_NAME ARRAY is an array of zero or more NUM_NAMES entries + * of NODE_NAME records. Each NODE_NAME entry represents an active + * name in the same NetBIOS scope as the requesting name in the + * local name table of the responder. RR_NAME is the requesting + * name. + */ + +#include <stdio.h> +#include <synch.h> +#include <pthread.h> +#include <strings.h> +#include <netinet/in.h> + +#include <smbsrv/libsmbns.h> + +#include <smbsrv/smbinfo.h> +#include <smbsrv/netbios.h> + +#define QUEUE_INSERT_TAIL(q, e) \ + ((e)->back) = (void *)((q)->back); \ + ((e)->forw) = (void *)(q); \ + ((q)->back->forw) = (void *)(e); \ + ((q)->back) = (void *)(e); + +#define QUEUE_CLIP(e) \ + (e)->forw->back = (e)->back; \ + (e)->back->forw = (e)->forw; \ + (e)->forw = 0; \ + (e)->back = 0; + +#define NETBIOS_NAME_SVC_LAUNCHED 0x00001 +#define NETBIOS_NAME_SVC_RUNNING 0x00002 +#define NETBIOS_NAME_SVC_FAILED 0x00004 + +#define NETBIOS_DATAGRAM_SVC_LAUNCHED 0x00010 +#define NETBIOS_DATAGRAM_SVC_RUNNING 0x00020 +#define NETBIOS_DATAGRAM_SVC_FAILED 0x00040 + +#define NETBIOS_TIMER_LAUNCHED 0x00100 +#define NETBIOS_TIMER_RUNNING 0x00200 +#define NETBIOS_TIMER_FAILED 0x00400 + +#define NETBIOS_BROWSER_LAUNCHED 0x01000 +#define NETBIOS_BROWSER_RUNNING 0x02000 +#define NETBIOS_BROWSER_FAILED 0x04000 + +#define NETBIOS_SHUTTING_DOWN 0x10000 +#define NETBIOS_SHUT_DOWN 0x20000 + +char smb_node_type; + +typedef struct { + mutex_t mtx; + cond_t cv; + uint32_t state; +} netbios_status_t; +extern netbios_status_t nb_status; + +/* + * NAME service definitions + */ +#define ADDR_FLAG_INVALID 0x0000 +#define ADDR_FLAG_VALID 0x0001 + +typedef struct addr_entry { + struct addr_entry *forw; + struct addr_entry *back; + uint32_t attributes; + uint32_t conflict_timer; + uint32_t refresh_ttl; + uint32_t ttl; + struct sockaddr_in sin; + int sinlen; + uint32_t flags; +} addr_entry_t; + +/* + * The NODE_NAME ARRAY is an array of zero or more NUM_NAMES entries + * of NODE_NAME records. Each NODE_NAME entry represents an active + * name in the same NetBIOS scope as the requesting name in the + * local name table of the responder. RR_NAME is the requesting + * name. + * + * NODE_NAME Entry: + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * +--- ---+ + * | | + * +--- NETBIOS FORMAT NAME ---+ + * | | + * +--- ---+ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The NAME_FLAGS field: + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | G | ONT |DRG|CNF|ACT|PRM| RESERVED | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * The NAME_FLAGS field is defined as: + * + * Symbol Bit(s) Description: + * + * RESERVED 7-15 Reserved for future use. Must be zero (0). + * PRM 6 Permanent Name Flag. If one (1) then entry + * is for the permanent node name. Flag is zero + * (0) for all other names. + * ACT 5 Active Name Flag. All entries have this flag + * set to one (1). + * CNF 4 Conflict Flag. If one (1) then name on this + * node is in conflict. + * DRG 3 Deregister Flag. If one (1) then this name + * is in the process of being deleted. + * ONT 1,2 Owner Node Type: + * 00 = B node + * 01 = P node + * 10 = M node + * 11 = Reserved for future use + * G 0 Group Name Flag. + * name. + * If zero (0) then it is a UNIQUE NetBIOS name. + */ + +typedef struct name_entry { + struct name_entry *forw; + struct name_entry *back; + unsigned char name[NETBIOS_NAME_SZ]; + unsigned char scope[NETBIOS_DOMAIN_NAME_MAX]; + unsigned short attributes; + struct addr_entry addr_list; + mutex_t mtx; +} name_entry; + +struct name_question { + struct name_entry *name; + unsigned question_type; + unsigned question_class; +}; + +struct resource_record { + /* + * These two flags and address are contained within RDATA + * when rr_type==0x0020 (NB - NetBIOS general Name Service) + * and rr_class==0x01 (IN - Internet Class). + */ + + struct name_entry *name; + unsigned short rr_type; + unsigned short rr_class; + uint32_t ttl; + unsigned short rdlength; + unsigned char *rdata; +}; + +struct name_packet { + unsigned short name_trn_id; + unsigned short info; + + unsigned qdcount; /* question entries */ + unsigned ancount; /* answer recs */ + unsigned nscount; /* authority recs */ + unsigned arcount; /* additional recs */ + + struct name_question *question; + struct resource_record *answer; + struct resource_record *authority; + struct resource_record *additional; + + unsigned char block_data[4]; /* begining of space */ +}; + +#define NAME_OPCODE_R 0x8000 /* RESPONSE flag: 1 bit */ +#define NAME_OPCODE_OPCODE_MASK 0x7800 /* OPCODE Field: 4 bits */ +#define NAME_OPCODE_QUERY 0x0000 +#define NAME_OPCODE_REGISTRATION 0x2800 +#define NAME_OPCODE_RELEASE 0x3000 +#define NAME_OPCODE_WACK 0x3800 +#define NAME_OPCODE_REFRESH 0x4000 +#define NAME_OPCODE_MULTIHOME 0x7800 +#define NAME_NM_FLAGS_AA 0x0400 /* Authoritative Answer:1 bit */ +#define NAME_NM_FLAGS_TC 0x0200 /* Truncation: 1 bit */ +#define NAME_NM_FLAGS_RD 0x0100 /* Recursion desired: 1 bit */ +#define NAME_NM_FLAGS_RA 0x0080 /* Recursion available: 1 bit */ +#define NAME_NM_FLAGS_x2 0x0040 /* reserved, mbz: 1 bit */ +#define NAME_NM_FLAGS_x1 0x0020 /* reserved, mbz: 1 bit */ +#define NAME_NM_FLAGS_B 0x0010 /* Broadcast: 1 bit */ +#define NAME_RCODE_MASK 0x000f /* RCODE Field: 4 bits */ +#define RCODE_FMT_ERR 0x0001 +#define RCODE_SRV_ERR 0x0002 +#define RCODE_NAM_ERR 0x0003 +#define RCODE_IMP_ERR 0x0004 +#define RCODE_RFS_ERR 0x0005 +#define RCODE_ACT_ERR 0x0006 +#define RCODE_CFT_ERR 0x0007 + +#define NM_FLAGS_UNICAST 0 +#define NM_FLAGS_BROADCAST NAME_NM_FLAGS_B + +#define PACKET_TYPE(x) ((x) & (NAME_OPCODE_R | NAME_OPCODE_OPCODE_MASK | \ + NAME_NM_FLAGS_AA | NAME_NM_FLAGS_RD)) + +#define RCODE(x) ((x) & NAME_RCODE_MASK) +#define POSITIVE_RESPONSE(x) (RCODE(x) == 0) +#define NEGATIVE_RESPONSE(x) (RCODE(x) != 0) + +#define END_NODE_CHALLENGE_REGISTRATION_REQUEST \ + (NAME_OPCODE_REGISTRATION | NAME_NM_FLAGS_AA | NAME_NM_FLAGS_RD) +#define END_NODE_CHALLENGE_NAME_REGISTRATION_RESPONSE \ + (NAME_OPCODE_R | END_NODE_CHALLENGE_REGISTRATION_REQUEST) + +#define NAME_QUERY_REQUEST \ + (NAME_OPCODE_QUERY | NAME_NM_FLAGS_RD) +#define NAME_QUERY_RESPONSE \ + (NAME_OPCODE_R | NAME_QUERY_REQUEST | \ + NAME_NM_FLAGS_AA | NAME_NM_FLAGS_RD) + +#define NODE_STATUS_REQUEST \ + (NAME_OPCODE_QUERY) +#define NODE_STATUS_RESPONSE \ + (NAME_OPCODE_R | NODE_STATUS_REQUEST | NAME_NM_FLAGS_AA) + +#define REDIRECT_NAME_QUERY_RESPONSE \ + (NAME_OPCODE_R | NAME_QUERY_REQUEST | NAME_NM_FLAGS_RD) + +#define NAME_REFRESH_REQUEST \ + (NAME_OPCODE_REFRESH) +#define NAME_REGISTRATION_REQUEST \ + (NAME_OPCODE_REGISTRATION | NAME_NM_FLAGS_RD) +#define NAME_MULTIHOME_REGISTRATION_REQUEST \ + (NAME_OPCODE_MULTIHOME | NAME_NM_FLAGS_RD) +#define NAME_REGISTRATION_RESPONSE \ + (NAME_OPCODE_R | NAME_REGISTRATION_REQUEST | NAME_NM_FLAGS_AA) + +#define NAME_RELEASE_REQUEST \ + (NAME_OPCODE_RELEASE) +#define NAME_RELEASE_RESPONSE \ + (NAME_OPCODE_R | NAME_RELEASE_REQUEST | NAME_NM_FLAGS_AA) + +#define WACK_RESPONSE \ + (NAME_OPCODE_R | NAME_OPCODE_WACK | NAME_NM_FLAGS_AA) + +#define NAME_QUESTION_TYPE_NB 0x0020 +#define NAME_QUESTION_TYPE_NBSTAT 0x0021 +#define NAME_QUESTION_CLASS_IN 0x0001 + + +#define NAME_RR_TYPE_A 0x0001 /* IP Address */ +#define NAME_RR_TYPE_NS 0x0002 /* Name Server */ +#define NAME_RR_TYPE_NULL 0x000A /* NULL */ +#define NAME_RR_TYPE_NB 0x0020 /* NetBIOS Name Service */ +#define NAME_RR_TYPE_NBSTAT 0x0021 /* NetBIOS Node Status */ + +#define NAME_RR_CLASS_IN 0x0001 /* NetBIOS Node Status */ + +#define NAME_NB_FLAGS_ONT_MASK (3<<13) +#define NAME_NB_FLAGS_ONT_B (0<<13) /* B-node (broadcast) */ +#define NAME_NB_FLAGS_ONT_P (1<<13) /* P-node (point-to-point) */ +#define NAME_NB_FLAGS_ONT_M (2<<13) /* M-node (multicast) */ +#define NAME_NB_FLAGS_ONT_resv (3<<13) +#define NAME_NB_FLAGS_G (1<<15) /* Group Name */ + +#define UNICAST 0 +#define BROADCAST 1 +#define POINTCAST 2 + +#define NAME_ATTR_UNIQUE 0x0000 +#define NAME_ATTR_GROUP 0x8000 +#define NAME_ATTR_OWNER_NODE_TYPE 0x6000 +#define NAME_ATTR_OWNER_TYPE_BNODE 0x0000 +#define NAME_ATTR_OWNER_TYPE_PNODE 0x2000 +#define NAME_ATTR_OWNER_TYPE_MNODE 0x4000 +#define NAME_ATTR_OWNER_TYPE_HNODE 0x6000 +#define NAME_ATTR_DEREGISTER 0x1000 +#define NAME_ATTR_CONFLICT 0x0800 +#define NAME_ATTR_ACTIVE_NAME 0x0400 +#define NAME_ATTR_PERMANENT 0x0200 +#define NAME_ATTR_RESERVED 0x01FF +#define NAME_ATTR_LOCAL 0x0001 + +#define NODE_TYPE(x) ((x) & NAME_ATTR_OWNER_NODE_TYPE)) +#define IS_BNODE(x) (NODE_TYPE(x) == NAME_ATTR_OWNER_TYPE_BNODE) +#define IS_PNODE(x) (NODE_TYPE(x) == NAME_ATTR_OWNER_TYPE_PNODE) +#define IS_MNODE(x) (NODE_TYPE(x) == NAME_ATTR_OWNER_TYPE_MNODE) +#define IS_HNODE(x) (NODE_TYPE(x) == NAME_ATTR_OWNER_TYPE_HNODE) + +#define IS_UNIQUE(x) (((x) & NAME_ATTR_GROUP) == 0) +#define IS_GROUP(x) (((x) & NAME_ATTR_GROUP) != 0) +#define IS_PERMANENT(x) (((x) & NAME_ATTR_PERMANENT) != 0) +#define IS_CONFLICTING(x) (((x) & NAME_ATTR_CONFLICT) != 0) +#define IS_ACTIVE(x) (((x) & NAME_ATTR_ACTIVE) != 0) +#define IS_DEGREGISTERED(x) (((x) & NAME_ATTR_ACTIVE) != 0) + +#define IS_LOCAL(x) (((x) & NAME_ATTR_LOCAL) != 0) +#define IS_PUBLIC(x) (((x) & NAME_ATTR_LOCAL) == 0) +#define PUBLIC_BITS(x) ((x) & ~NAME_ATTR_RESERVED) + +#define SAME_SCOPE(scope, e) (strcmp((scope), ((e)->scope)) == 0) + +/* + * STATISTICS Field of the NODE STATUS RESPONSE: + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | UNIT_ID (Unique unit ID) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | UNIT_ID,continued | JUMPERS | TEST_RESULT | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | VERSION_NUMBER | PERIOD_OF_STATISTICS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_OF_CRCs | NUMBER_ALIGNMENT_ERRORS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_OF_COLLISIONS | NUMBER_SEND_ABORTS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_GOOD_SENDS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_GOOD_RECEIVES | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_RETRANSMITS | NUMBER_NO_RESOURCE_CONDITIONS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_FREE_COMMAND_BLOCKS | TOTAL_NUMBER_COMMAND_BLOCKS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |MAX_TOTAL_NUMBER_COMMAND_BLOCKS| NUMBER_PENDING_SESSIONS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MAX_NUMBER_PENDING_SESSIONS | MAX_TOTAL_SESSIONS_POSSIBLE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SESSION_DATA_PACKET_SIZE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +typedef struct { + unsigned char unit_id[6]; + unsigned char jumpers; + unsigned char test_result; + unsigned short version_number; + unsigned short statistical_period; + unsigned short crc_errors; + unsigned short alignment_errors; + unsigned short collisions; + unsigned short send_aborts; + unsigned int good_sends; + unsigned int good_receives; + unsigned short retransmits; + unsigned short no_resource_conditions; + unsigned short free_command_blocks; + unsigned short total_command_blocks; + unsigned short max_total_command_blocks; + unsigned short pending_sessions; + unsigned short max_pending_sessions; + unsigned short total_possible_sessions; + unsigned short session_data_packet_size; +} node_status_response; + +/* + * 4.4.1. NetBIOS DATAGRAM HEADER + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MSG_TYPE | FLAGS | DGM_ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_IP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_PORT | DGM_LENGTH | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | PACKET_OFFSET | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +typedef struct { + unsigned char msg_type; + unsigned char flags; + unsigned short dgm_id; + uint32_t source_ip; + unsigned short source_port; + unsigned short dgm_length; + unsigned short packet_offset; +} datagram_header; + +/* + * MSG_TYPE values (in hexidecimal): + * + * 10 - DIRECT_UNIQUE DATAGRAM + * 11 - DIRECT_GROUP DATAGRAM + * 12 - BROADCAST DATAGRAM + * 13 - DATAGRAM ERROR + * 14 - DATAGRAM QUERY REQUEST + * 15 - DATAGRAM POSITIVE QUERY RESPONSE + * 16 - DATAGRAM NEGATIVE QUERY RESPONSE + */ +#define DATAGRAM_TYPE_DIRECT_UNIQUE 0x10 +#define DATAGRAM_TYPE_DIRECT_GROUP 0x11 +#define DATAGRAM_TYPE_BROADCAST 0x12 +#define DATAGRAM_TYPE_ERROR_DATAGRAM 0x13 +#define DATAGRAM_TYPE_QUERY_REQUEST 0x14 +#define DATAGRAM_TYPE_POSITIVE_RESPONSE 0x15 +#define DATAGRAM_TYPE_NEGATIVE_RESPONSE 0x16 + + +/* + * Bit definitions of the FLAGS field: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 0 | SNT | F | M | + * +---+---+---+---+---+---+---+---+ + * + * Symbol Bit(s) Description + * + * M 7 MORE flag, If set then more NetBIOS datagram + * fragments follow. + * + * F 6 FIRST packet flag, If set then this is first + * (and possibly only) fragment of NetBIOS + * datagram + * + * SNT 4,5 Source End-Node type: + * 00 = B node + * 01 = P node + * 10 = M node + * 11 = H node + * RESERVED 0-3 Reserved, must be zero (0) + */ +#define DATAGRAM_FLAGS_MORE 0x01 +#define DATAGRAM_FLAGS_FIRST 0x02 +#define DATAGRAM_FLAGS_SRC_TYPE 0x0c +#define DATAGRAM_FLAGS_B_NODE 0x00 +#define DATAGRAM_FLAGS_P_NODE 0x04 +#define DATAGRAM_FLAGS_M_NODE 0x08 +#define DATAGRAM_FLAGS_H_NODE 0x0C +#define DATAGRAM_FLAGS_NBDD 0x0c +#define DATAGRAM_FLAGS_RESERVED 0xf0 + +/* + * 4.4.2. DIRECT_UNIQUE, DIRECT_GROUP, & BROADCAST DATAGRAM + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MSG_TYPE | FLAGS | DGM_ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_IP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_PORT | DGM_LENGTH | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | PACKET_OFFSET | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | + * / SOURCE_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / DESTINATION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / USER_DATA / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +typedef struct { + datagram_header header; + unsigned char *source_name; + unsigned char *destination_name; + unsigned char *user_data; +} datagram_packet; + + +/* + * 4.4.3. DATAGRAM ERROR PACKET + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MSG_TYPE | FLAGS | DGM_ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_IP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_PORT | ERROR_CODE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * ERROR_CODE values (in hexidecimal): + * + * 82 - DESTINATION NAME NOT PRESENT + * 83 - INVALID SOURCE NAME FORMAT + * 84 - INVALID DESTINATION NAME FORMAT + */ + +typedef struct { + unsigned char msg_type; + unsigned char flags; + unsigned short dgm_id; + uint32_t source_ip; + unsigned short source_port; + unsigned char error; +} datagram_error_packet; + +/* + * 4.4.4. DATAGRAM QUERY REQUEST + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MSG_TYPE | FLAGS | DGM_ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_IP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_PORT | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + * | | + * / DESTINATION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 4.4.5. DATAGRAM POSITIVE AND NEGATIVE QUERY RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MSG_TYPE | FLAGS | DGM_ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_IP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_PORT | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + * | | + * / DESTINATION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +typedef struct datagram_query_packet { + unsigned char msg_type; + unsigned char flags; + unsigned short dgm_id; + uint32_t source_ip; + unsigned short source_port; + unsigned char destination_name[MAX_NAME_LENGTH]; +} datagram_query_packet; + + +typedef struct datagram { + struct datagram *forw; + struct datagram *back; + struct addr_entry inaddr; + int discard_timer; + unsigned char packet_type; + unsigned char flags; + unsigned short datagram_id; + struct name_entry src; + struct name_entry dest; + unsigned short offset; + unsigned short data_length; + unsigned char *data; + unsigned int rawbytes; + unsigned char rawbuf[MAX_DATAGRAM_LENGTH]; +} datagram; + +typedef struct datagram_queue { + struct datagram *forw; + struct datagram *back; +} datagram_queue; + +typedef struct name_queue { + struct name_entry head; + mutex_t mtx; +} name_queue_t; + +#define NETBIOS_EMPTY_NAME (unsigned char *)"" + +#define NETBIOS_NAME_IS_STAR(name) \ + (bcmp(name, "*\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", NETBIOS_NAME_SZ) == 0) + +void smb_netbios_chg_status(uint32_t status, int set); + +/* + * Name Cache Functions + */ +int smb_netbios_cache_init(void); +void smb_netbios_cache_fini(void); +void smb_netbios_cache_dump(void); +void smb_netbios_cache_print(void); +void smb_netbios_cache_diag(char ** pbuf); +int smb_netbios_cache_count(void); +void smb_netbios_cache_clean(void); +void smb_netbios_cache_reset_ttl(void); +void smb_netbios_cache_delete_locals(name_queue_t *delq); +void smb_netbios_cache_refresh(name_queue_t *refq); + +int smb_netbios_cache_insert(struct name_entry *name); +int smb_netbios_cache_insert_list(struct name_entry *name); +void smb_netbios_cache_delete(struct name_entry *name); +int smb_netbios_cache_delete_addr(struct name_entry *name); +struct name_entry *smb_netbios_cache_lookup(struct name_entry *name); +struct name_entry *smb_netbios_cache_lookup_addr(struct name_entry *name); +void smb_netbios_cache_update_entry(struct name_entry *entry, + struct name_entry *name); +void smb_netbios_cache_unlock_entry(struct name_entry *name); +unsigned char *smb_netbios_cache_status(unsigned char *buf, int bufsize, + unsigned char *scope); + +void smb_netbios_name_dump(struct name_entry *entry); +void smb_netbios_name_logf(struct name_entry *entry); +void smb_netbios_name_freeaddrs(struct name_entry *entry); +struct name_entry *smb_netbios_name_dup(struct name_entry *entry, + int alladdr); + +/* Name service functions */ +void *smb_netbios_name_service_daemon(void *); +void smb_init_name_struct(unsigned char *, char, + unsigned char *, uint32_t, unsigned short, + uint32_t, uint32_t, struct name_entry *); + +struct name_entry *smb_name_find_name(struct name_entry *name); +int smb_name_add_name(struct name_entry *name); +int smb_name_delete_name(struct name_entry *name); +void smb_name_unlock_name(struct name_entry *name); + +void smb_netbios_name_config(void); +void smb_netbios_name_unconfig(void); +void smb_netbios_name_tick(void); + +int smb_first_level_name_encode(struct name_entry *name, + unsigned char *out, int max_out); +int smb_first_level_name_decode(unsigned char *in, + struct name_entry *name); +void smb_encode_netbios_name(unsigned char *name, + char suffix, unsigned char *scope, + struct name_entry *dest); + +/* Datagram service functions */ +void *smb_netbios_datagram_service_daemon(void *); +int smb_netbios_datagram_send(struct name_entry *, + struct name_entry *, unsigned char *, int); +void smb_netbios_datagram_tick(void); + + +/* browser functions */ +void smb_browser_config(void); +void *smb_browser_dispatch(void *arg); +void *smb_browser_daemon(void *); +int smb_net_id(uint32_t ipaddr); +struct name_entry *smb_browser_get_srvname(unsigned short netid); +int smb_browser_load_transact_header(unsigned char *buffer, + int maxcnt, int data_count, int reply, char *mailbox); + +/* Netlogon function */ +/* + * smb_netlogon_receive + * + * This is where we handle all incoming NetLogon messages. Currently, we + * ignore requests from anyone else. We are only interested in responses + * to our own requests. The NetLogonResponse provides the name of the PDC. + * If we don't already have a controller name, we use the name provided + * in the message. Otherwise we use the name already in the environment. + */ +void smb_netlogon_receive(struct datagram *datagram, char *mailbox, + unsigned char *data, int datalen); + +#endif /* _SMB_NETBIOS_H_ */ diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_cache.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_cache.c new file mode 100644 index 0000000000..1e47658700 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_cache.c @@ -0,0 +1,776 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <synch.h> +#include <stdio.h> +#include <syslog.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <smbsrv/libsmbns.h> +#include <smbns_netbios.h> + +#define NETBIOS_HTAB_SZ 128 +#define NETBIOS_HKEY_SZ (NETBIOS_NAME_SZ + NETBIOS_DOMAIN_NAME_MAX) + +#define NETBIOS_NAMEBUF char namebuf[20] + +#define NETBIOS_SAME_IP(addr1, addr2) \ + ((addr1)->sin.sin_addr.s_addr == (addr2)->sin.sin_addr.s_addr) + +int smb_netbios_name_debug = 0; + +typedef char nb_key_t[NETBIOS_HKEY_SZ]; +static HT_HANDLE *smb_netbios_cache = 0; +static rwlock_t nb_cache_lock; + +static char *smb_strname(struct name_entry *name, char *buf, int bufsize); +static void hash_callback(HT_ITEM *item); +static int smb_netbios_match(const char *key1, const char *key2, size_t n); +static void smb_netbios_cache_key(char *key, unsigned char *name, + unsigned char *scope); + +int +smb_netbios_cache_init() +{ + (void) rw_wrlock(&nb_cache_lock); + if (smb_netbios_cache == 0) { + smb_netbios_cache = ht_create_table(NETBIOS_HTAB_SZ, + NETBIOS_HKEY_SZ, HTHF_FIXED_KEY); + if (smb_netbios_cache == 0) { + syslog(LOG_ERR, + "smbd: cannot create NetBIOS name cache"); + (void) rw_unlock(&nb_cache_lock); + return (0); + } + (void) ht_register_callback(smb_netbios_cache, hash_callback); + ht_set_cmpfn(smb_netbios_cache, smb_netbios_match); + } + (void) rw_unlock(&nb_cache_lock); + + return (1); +} + +void +smb_netbios_cache_fini() +{ + (void) rw_wrlock(&nb_cache_lock); + ht_destroy_table(smb_netbios_cache); + smb_netbios_cache = 0; + (void) rw_unlock(&nb_cache_lock); +} + +void +smb_netbios_cache_clean() +{ + (void) rw_wrlock(&nb_cache_lock); + (void) ht_clean_table(smb_netbios_cache); + (void) rw_unlock(&nb_cache_lock); +} + +/* + * smb_netbios_cache_lookup + * + * Searches the name cache for the given entry, if found + * the entry will be locked before returning to caller + * so caller MUST unlock the entry after it's done with it. + */ +struct name_entry * +smb_netbios_cache_lookup(struct name_entry *name) +{ + HT_ITEM *item; + nb_key_t key; + struct name_entry *entry = NULL; + unsigned char scope[SMB_PI_MAX_SCOPE]; + unsigned char hostname[MAXHOSTNAMELEN]; + + if (NETBIOS_NAME_IS_STAR(name->name)) { + /* Return our address */ + smb_config_rdlock(); + (void) strlcpy((char *)scope, + smb_config_getstr(SMB_CI_NBSCOPE), sizeof (scope)); + (void) utf8_strupr((char *)scope); + smb_config_unlock(); + + if (smb_getnetbiosname((char *)hostname, MAXHOSTNAMELEN) != 0) + return (NULL); + + smb_encode_netbios_name(hostname, 0x00, scope, name); + } + + (void) rw_rdlock(&nb_cache_lock); + + smb_netbios_cache_key(key, name->name, name->scope); + item = ht_find_item(smb_netbios_cache, key); + if (item) { + entry = (struct name_entry *)item->hi_data; + (void) mutex_lock(&entry->mtx); + if ((entry->attributes & NAME_ATTR_CONFLICT) != 0) { + (void) mutex_unlock(&entry->mtx); + entry = NULL; + } + } + + (void) rw_unlock(&nb_cache_lock); + return (entry); +} + +void +smb_netbios_cache_unlock_entry(struct name_entry *name) +{ + if (name) + (void) mutex_unlock(&name->mtx); +} + +/* + * smb_netbios_cache_lookup_addr + * + * lookup the given 'name' in the cache and then checks + * if the address also matches with the found entry. + * 'name' is supposed to contain only one address. + * + * The found entry will be locked before returning to caller + * so caller MUST unlock the entry after it's done with it. + */ +struct name_entry * +smb_netbios_cache_lookup_addr(struct name_entry *name) +{ + struct name_entry *entry = 0; + struct addr_entry *addr; + struct addr_entry *name_addr; + HT_ITEM *item; + nb_key_t key; + + (void) rw_rdlock(&nb_cache_lock); + smb_netbios_cache_key(key, name->name, name->scope); + item = ht_find_item(smb_netbios_cache, key); + + if (item && item->hi_data) { + name_addr = &name->addr_list; + entry = (struct name_entry *)item->hi_data; + (void) mutex_lock(&entry->mtx); + addr = &entry->addr_list; + do { + if (NETBIOS_SAME_IP(addr, name_addr)) { + /* note that entry lock isn't released here */ + (void) rw_unlock(&nb_cache_lock); + return (entry); + } + addr = addr->forw; + } while (addr != &entry->addr_list); + (void) mutex_unlock(&entry->mtx); + } + + (void) rw_unlock(&nb_cache_lock); + return (0); +} + +int +smb_netbios_cache_insert(struct name_entry *name) +{ + struct name_entry *entry; + struct addr_entry *addr; + struct addr_entry *name_addr; + HT_ITEM *item; + nb_key_t key; + + /* No point in adding a name with IP address 255.255.255.255 */ + if (name->addr_list.sin.sin_addr.s_addr == 0xffffffff) + return (0); + + (void) rw_wrlock(&nb_cache_lock); + smb_netbios_cache_key(key, name->name, name->scope); + item = ht_find_item(smb_netbios_cache, key); + + if (item && item->hi_data) { + /* Name already exists */ + entry = (struct name_entry *)item->hi_data; + (void) mutex_lock(&entry->mtx); + + name_addr = &name->addr_list; + addr = &entry->addr_list; + if (NETBIOS_SAME_IP(addr, name_addr) && + (addr->sin.sin_port == name_addr->sin.sin_port)) { + entry->attributes |= + name_addr->attributes & NAME_ATTR_LOCAL; + syslog(LOG_DEBUG, "cache_insert: exists"); + (void) mutex_unlock(&entry->mtx); + (void) rw_unlock(&nb_cache_lock); + return (0); /* exists */ + } + + /* Was not primary: looks for others */ + for (addr = entry->addr_list.forw; + addr != &entry->addr_list; addr = addr->forw) { + if (NETBIOS_SAME_IP(addr, name_addr) && + (addr->sin.sin_port == name_addr->sin.sin_port)) { + syslog(LOG_DEBUG, "cache_insert: dup"); + (void) mutex_unlock(&entry->mtx); + (void) rw_unlock(&nb_cache_lock); + return (0); /* exists */ + } + } + + addr = (struct addr_entry *)malloc(sizeof (struct addr_entry)); + if (addr == 0) { + (void) mutex_unlock(&entry->mtx); + (void) rw_unlock(&nb_cache_lock); + return (-1); + } + *addr = name->addr_list; + entry->attributes |= addr->attributes; + QUEUE_INSERT_TAIL(&entry->addr_list, addr); + (void) mutex_unlock(&entry->mtx); + (void) rw_unlock(&nb_cache_lock); + return (0); + } + + entry = (struct name_entry *)malloc(sizeof (struct name_entry)); + if (entry == 0) { + (void) rw_unlock(&nb_cache_lock); + return (-1); + } + *entry = *name; + entry->addr_list.forw = entry->addr_list.back = &entry->addr_list; + entry->attributes |= entry->addr_list.attributes; + (void) mutex_init(&entry->mtx, 0, 0); + if (ht_replace_item(smb_netbios_cache, key, entry) == 0) { + free(entry); + (void) rw_unlock(&nb_cache_lock); + return (-1); + } + + (void) rw_unlock(&nb_cache_lock); + return (0); +} + + +void +smb_netbios_cache_delete(struct name_entry *name) +{ + nb_key_t key; + HT_ITEM *item; + struct name_entry *entry; + + (void) rw_wrlock(&nb_cache_lock); + smb_netbios_cache_key(key, name->name, name->scope); + item = ht_find_item(smb_netbios_cache, key); + if (item && item->hi_data) { + entry = (struct name_entry *)item->hi_data; + (void) mutex_lock(&entry->mtx); + ht_mark_delete(smb_netbios_cache, item); + (void) mutex_unlock(&entry->mtx); + } + (void) rw_unlock(&nb_cache_lock); +} + +/* + * smb_netbios_cache_insert_list + * + * Insert a name with multiple addresses + */ +int +smb_netbios_cache_insert_list(struct name_entry *name) +{ + struct name_entry entry; + struct addr_entry *addr; + + addr = &name->addr_list; + do { + smb_init_name_struct(NETBIOS_EMPTY_NAME, 0, name->scope, + addr->sin.sin_addr.s_addr, + addr->sin.sin_port, + name->attributes, + addr->attributes, + &entry); + (void) memcpy(entry.name, name->name, NETBIOS_NAME_SZ); + entry.addr_list.refresh_ttl = entry.addr_list.ttl = + addr->refresh_ttl; + (void) smb_netbios_cache_insert(&entry); + addr = addr->forw; + } while (addr != &name->addr_list); + + return (0); +} + +void +smb_netbios_cache_update_entry(struct name_entry *entry, + struct name_entry *name) +{ + struct addr_entry *addr; + struct addr_entry *name_addr; + + addr = &entry->addr_list; + name_addr = &name->addr_list; + + if (IS_UNIQUE(entry->attributes)) { + do { + addr->ttl = name_addr->ttl; + addr = addr->forw; + } while (addr != &entry->addr_list); + + } else { + do { + if (NETBIOS_SAME_IP(addr, name_addr) && + (addr->sin.sin_port == name_addr->sin.sin_port)) { + addr->ttl = name_addr->ttl; + return; + } + addr = addr->forw; + } while (addr != &entry->addr_list); + } +} + +/* + * smb_netbios_cache_status + * + * Scan the name cache and gather status for + * Node Status response for names in the given scope + */ +unsigned char * +smb_netbios_cache_status(unsigned char *buf, int bufsize, unsigned char *scope) +{ + HT_ITERATOR hti; + HT_ITEM *item; + struct name_entry *name; + unsigned char *numnames; + unsigned char *scan; + unsigned char *scan_end; + + scan = buf; + scan_end = scan + bufsize; + + numnames = scan++; + *numnames = 0; + + (void) rw_rdlock(&nb_cache_lock); + item = ht_findfirst(smb_netbios_cache, &hti); + do { + if (item == 0) + break; + + if (item->hi_data == 0) + continue; + + if ((scan + NETBIOS_NAME_SZ + 2) >= scan_end) + /* no room for adding next entry */ + break; + + name = (struct name_entry *)item->hi_data; + (void) mutex_lock(&name->mtx); + + if (IS_LOCAL(name->attributes) && + (strcasecmp((char *)scope, (char *)name->scope) == 0)) { + bcopy(name->name, scan, NETBIOS_NAME_SZ); + scan += NETBIOS_NAME_SZ; + *scan++ = PUBLIC_BITS(name->attributes) >> 8; + *scan++ = PUBLIC_BITS(name->attributes); + (*numnames)++; + } + + (void) mutex_unlock(&name->mtx); + } while ((item = ht_findnext(&hti)) != 0); + (void) rw_unlock(&nb_cache_lock); + + return (scan); +} + +void +smb_netbios_cache_reset_ttl() +{ + struct addr_entry *addr; + struct name_entry *name; + HT_ITERATOR hti; + HT_ITEM *item; + + (void) rw_rdlock(&nb_cache_lock); + item = ht_findfirst(smb_netbios_cache, &hti); + do { + if (item == 0) + break; + + if (item->hi_data == 0) + continue; + + name = (struct name_entry *)item->hi_data; + (void) mutex_lock(&name->mtx); + + addr = &name->addr_list; + do { + if (addr->ttl < 1) { + if (addr->refresh_ttl) + addr->ttl = addr->refresh_ttl; + else + addr->refresh_ttl = addr->ttl = + TO_SECONDS(DEFAULT_TTL); + } + addr = addr->forw; + } while (addr != &name->addr_list); + + (void) mutex_unlock(&name->mtx); + } while ((item = ht_findnext(&hti)) != 0); + (void) rw_unlock(&nb_cache_lock); +} + +/* + * Returns TRUE when given name is added to the refresh queue + * FALSE if not. + */ +static boolean_t +smb_netbios_cache_insrefq(name_queue_t *refq, HT_ITEM *item) +{ + struct name_entry *name; + struct name_entry *refent; + + name = (struct name_entry *)item->hi_data; + + if (IS_LOCAL(name->attributes)) { + if (IS_UNIQUE(name->attributes)) { + refent = smb_netbios_name_dup(name, 1); + if (refent) + QUEUE_INSERT_TAIL(&refq->head, refent) + + /* next name */ + return (B_TRUE); + } + } else { + ht_mark_delete(smb_netbios_cache, item); + refent = smb_netbios_name_dup(name, 0); + if (refent) + QUEUE_INSERT_TAIL(&refq->head, refent) + + /* next name */ + return (B_TRUE); + } + + return (B_FALSE); +} + +/* + * smb_netbios_cache_refresh + * + * Scans the name cache and add all local unique names + * and non-local names the passed refresh queue. Non- + * local names will also be marked as deleted. + * + * NOTE that the caller MUST protect the queue using + * its mutex + */ +void +smb_netbios_cache_refresh(name_queue_t *refq) +{ + struct name_entry *name; + struct addr_entry *addr; + HT_ITERATOR hti; + HT_ITEM *item; + + bzero(&refq->head, sizeof (refq->head)); + refq->head.forw = refq->head.back = &refq->head; + + (void) rw_rdlock(&nb_cache_lock); + item = ht_findfirst(smb_netbios_cache, &hti); + do { /* name loop */ + if (item == 0) + break; + + if (item->hi_data == 0) + continue; + + name = (struct name_entry *)item->hi_data; + (void) mutex_lock(&name->mtx); + + addr = &name->addr_list; + do { /* address loop */ + if (addr->ttl > 0) { + addr->ttl--; + if (addr->ttl == 0) { + if (smb_netbios_cache_insrefq(refq, + item)) + break; + } + } + addr = addr->forw; + } while (addr != &name->addr_list); + + (void) mutex_unlock(&name->mtx); + } while ((item = ht_findnext(&hti)) != 0); + (void) rw_unlock(&nb_cache_lock); +} + +/* + * smb_netbios_cache_delete_locals + * + * Scans the name cache and add all local names to + * the passed delete queue. + * + * NOTE that the caller MUST protect the queue using + * its mutex + */ +void +smb_netbios_cache_delete_locals(name_queue_t *delq) +{ + struct name_entry *entry; + struct name_entry *delent; + HT_ITERATOR hti; + HT_ITEM *item; + + bzero(&delq->head, sizeof (delq->head)); + delq->head.forw = delq->head.back = &delq->head; + + (void) rw_wrlock(&nb_cache_lock); + item = ht_findfirst(smb_netbios_cache, &hti); + do { + if (item == 0) + break; + + if (item->hi_data == 0) + continue; + + entry = (struct name_entry *)item->hi_data; + (void) mutex_lock(&entry->mtx); + + if (IS_LOCAL(entry->attributes)) { + ht_mark_delete(smb_netbios_cache, item); + delent = smb_netbios_name_dup(entry, 1); + if (delent) + QUEUE_INSERT_TAIL(&delq->head, delent) + } + + (void) mutex_unlock(&entry->mtx); + } while ((item = ht_findnext(&hti)) != 0); + (void) rw_unlock(&nb_cache_lock); +} + +void +smb_netbios_name_freeaddrs(struct name_entry *entry) +{ + struct addr_entry *addr; + + if (entry == 0) + return; + + while ((addr = entry->addr_list.forw) != &entry->addr_list) { + QUEUE_CLIP(addr); + free(addr); + } +} + +/* + * smb_netbios_cache_count + * + * Returns the number of names in the cache + */ +int +smb_netbios_cache_count() +{ + int cnt; + + (void) rw_rdlock(&nb_cache_lock); + cnt = ht_get_total_items(smb_netbios_cache); + (void) rw_unlock(&nb_cache_lock); + + return (cnt); +} + +void +smb_netbios_cache_dump(void) +{ + struct name_entry *name; + HT_ITERATOR hti; + HT_ITEM *item; + + (void) rw_rdlock(&nb_cache_lock); + item = ht_findfirst(smb_netbios_cache, &hti); + while (item) { + if (item->hi_data) { + name = (struct name_entry *)item->hi_data; + (void) mutex_lock(&name->mtx); + smb_netbios_name_dump(name); + (void) mutex_unlock(&name->mtx); + } + item = ht_findnext(&hti); + } + (void) rw_unlock(&nb_cache_lock); +} + +void +smb_netbios_name_dump(struct name_entry *entry) +{ + struct addr_entry *addr; + int count = 0; + + if (smb_netbios_name_debug == 0) + return; + + syslog(LOG_DEBUG, "name='%15.15s<%02X>' scope='%s' attr=0x%x", + entry->name, entry->name[15], + entry->scope, entry->attributes); + addr = &entry->addr_list; + do { + syslog(LOG_DEBUG, "addr_list[%d]:", count++); + syslog(LOG_DEBUG, " attributes = 0x%x", addr->attributes); + syslog(LOG_DEBUG, " conflict_timer = %d", + addr->conflict_timer); + syslog(LOG_DEBUG, " refresh_ttl = %d", addr->refresh_ttl); + syslog(LOG_DEBUG, " ttl = %d", addr->ttl); + syslog(LOG_DEBUG, " sin.sin_addr = %s", + inet_ntoa(addr->sin.sin_addr)); + syslog(LOG_DEBUG, " sin.sin_port = %d", addr->sin.sin_port); + syslog(LOG_DEBUG, " sin.sinlen = %d", addr->sinlen); + addr = addr->forw; + } while (addr != &entry->addr_list); +} + +void +smb_netbios_name_logf(struct name_entry *entry) +{ + struct addr_entry *addr; + NETBIOS_NAMEBUF; + + (void) smb_strname(entry, namebuf, sizeof (namebuf)); + syslog(LOG_DEBUG, "%s flags=0x%x\n", namebuf, entry->attributes); + addr = &entry->addr_list; + do { + syslog(LOG_DEBUG, " %s ttl=%d flags=0x%x", + inet_ntoa(addr->sin.sin_addr), + addr->ttl, addr->attributes); + addr = addr->forw; + } while (addr && (addr != &entry->addr_list)); +} + +/* + * smb_netbios_name_dup + * + * Duplicate the given name entry. If 'alladdr' is 0 only + * copy the primary address otherwise duplicate all the + * addresses. NOTE that the duplicate structure is not + * like a regular cache entry i.e. it's a contiguous block + * of memory and each addr structure doesn't have it's own + * allocated memory. So, the returned structure can be freed + * by one free call. + */ +struct name_entry * +smb_netbios_name_dup(struct name_entry *entry, int alladdr) +{ + struct addr_entry *addr; + struct addr_entry *dup_addr; + struct name_entry *dup; + int addr_cnt = 0; + int size = 0; + + if (alladdr) { + addr = entry->addr_list.forw; + while (addr && (addr != &entry->addr_list)) { + addr_cnt++; + addr = addr->forw; + } + } + + size = sizeof (struct name_entry) + + (addr_cnt * sizeof (struct addr_entry)); + dup = (struct name_entry *)malloc(size); + if (dup == 0) + return (0); + + bzero(dup, size); + + dup->forw = dup->back = dup; + dup->attributes = entry->attributes; + (void) memcpy(dup->name, entry->name, NETBIOS_NAME_SZ); + (void) strlcpy((char *)dup->scope, (char *)entry->scope, + NETBIOS_DOMAIN_NAME_MAX); + dup->addr_list = entry->addr_list; + dup->addr_list.forw = dup->addr_list.back = &dup->addr_list; + + if (alladdr == 0) + return (dup); + + /* LINTED - E_BAD_PTR_CAST_ALIGN */ + dup_addr = (struct addr_entry *)((unsigned char *)dup + + sizeof (struct name_entry)); + + addr = entry->addr_list.forw; + while (addr && (addr != &entry->addr_list)) { + *dup_addr = *addr; + QUEUE_INSERT_TAIL(&dup->addr_list, dup_addr); + addr = addr->forw; + dup_addr++; + } + + return (dup); +} + +static char * +smb_strname(struct name_entry *name, char *buf, int bufsize) +{ + char *p; + + (void) snprintf(buf, bufsize, "%15.15s", name->name); + p = strchr(buf, ' '); + if (p) + (void) snprintf(p, 5, "<%02X>", name->name[15]); + + return (buf); +} + +static void +hash_callback(HT_ITEM *item) +{ + struct name_entry *entry; + + if (item && item->hi_data) { + entry = (struct name_entry *)item->hi_data; + smb_netbios_name_freeaddrs(entry); + free(entry); + } +} + + +/*ARGSUSED*/ +static int +smb_netbios_match(const char *key1, const char *key2, size_t n) +{ + int res; + + res = bcmp(key1, key2, NETBIOS_NAME_SZ); + if (res == 0) { + /* Names are the same, compare scopes */ + res = strcmp(key1 + NETBIOS_NAME_SZ, key2 + NETBIOS_NAME_SZ); + } + + return (res); +} + +static void +smb_netbios_cache_key(char *key, unsigned char *name, unsigned char *scope) +{ + bzero(key, NETBIOS_HKEY_SZ); + (void) memcpy(key, name, NETBIOS_NAME_SZ); + (void) memcpy(key + NETBIOS_NAME_SZ, scope, + strlen((const char *)scope)); +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_datagram.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_datagram.c new file mode 100644 index 0000000000..2775ccfe7a --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_datagram.c @@ -0,0 +1,1061 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Description: + * + * Contains base code for netbios datagram service. + * + * Relavent sections from RFC1002: + * + * 5.3. NetBIOS DATAGRAM SERVICE PROTOCOLS + * + * The following are GLOBAL variables and should be NetBIOS user + * configurable: + * + * - SCOPE_ID: the non-leaf section of the domain name preceded by a + * '.' which represents the domain of the NetBIOS scope for the + * NetBIOS name. The following protocol description only supports + * single scope operation. + * + * - MAX_DATAGRAM_LENGTH: the maximum length of an IP datagram. The + * minimal maximum length defined in for IP is 576 bytes. This + * value is used when determining whether to fragment a NetBIOS + * datagram. Implementations are expected to be capable of + * receiving unfragmented NetBIOS datagrams up to their maximum + * size. + * + * - BROADCAST_ADDRESS: the IP address B-nodes use to send datagrams + * with group name destinations and broadcast datagrams. The + * default is the IP broadcast address for a single IP network. + * + * + * The following are Defined Constants for the NetBIOS Datagram + * Service: + * + * - DGM_SRVC_UDP_PORT: the globally well-known UDP port allocated + * where the NetBIOS Datagram Service receives UDP packets. See + * section 6, "Defined Constants", for its value. + */ + +/* + * + * 6. DEFINED CONSTANTS AND VARIABLES + * + * GENERAL: + * + * SCOPE_ID The name of the NetBIOS scope. + * + * This is expressed as a character + * string meeting the requirements of + * the domain name system and without + * a leading or trailing "dot". + * + * An implementation may elect to make + * this a single global value for the + * node or allow it to be specified + * with each separate NetBIOS name + * (thus permitting cross-scope + * references.) + * + * BROADCAST_ADDRESS An IP address composed of the + * node network and subnetwork + * numbers with all remaining bits set + * to one. + * + * I.e. "Specific subnet" broadcast + * addressing according to section 2.3 + * of RFC 950. + * + * BCAST_REQ_RETRY_TIMEOUT 250 milliseconds. + * An adaptive timer may be used. + * + * BCAST_REQ_RETRY_COUNT 3 + * + * UCAST_REQ_RETRY_TIMEOUT 5 seconds + * An adaptive timer may be used. + * + * UCAST_REQ_RETRY_COUNT 3 + * + * MAX_DATAGRAM_LENGTH 576 bytes (default) + * + * DATAGRAM SERVICE: + * + * DGM_SRVC_UDP_PORT 138 (decimal) + * + * FRAGMENT_TO 2 seconds (default) + */ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <strings.h> +#include <syslog.h> +#include <synch.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include <smbns_netbios.h> + +#include <smbsrv/libsmbns.h> + +static int datagram_sock = -1; +static short datagram_id = 1; +static struct datagram_queue smb_datagram_queue; +static mutex_t smb_dgq_mtx; + +/* + * Function: smb_netbios_datagram_tick(void) + * + * Description: + * + * Called once a second to handle time to live timeouts in + * datagram assembly queue. + * + * Inputs: + * + * Returns: + * void -> Nothing at all... + */ + +void +smb_netbios_datagram_tick(void) +{ + struct datagram *entry; + struct datagram *next; + + (void) mutex_lock(&smb_dgq_mtx); + + for (entry = smb_datagram_queue.forw; + entry != (struct datagram *)((uintptr_t)&smb_datagram_queue); + entry = next) { + next = entry->forw; + if (--entry->discard_timer == 0) { + /* Toss it */ + QUEUE_CLIP(entry); + free(entry); + } + } + (void) mutex_unlock(&smb_dgq_mtx); +} + +void +smb_netbios_datagram_fini() +{ + struct datagram *entry; + + (void) mutex_lock(&smb_dgq_mtx); + while ((entry = smb_datagram_queue.forw) != + (struct datagram *)((uintptr_t)&smb_datagram_queue)) { + QUEUE_CLIP(entry); + free(entry); + } + (void) mutex_unlock(&smb_dgq_mtx); +} + +/* + * Function: int smb_netbios_send_Bnode_datagram(unsigned char *data, + * struct name_entry *source, struct name_entry *destination, + * uint32_t broadcast) + * + * Description from rfc1002: + * + * 5.3.1. B NODE TRANSMISSION OF NetBIOS DATAGRAMS + * + * PROCEDURE send_datagram(data, source, destination, broadcast) + * + * (* + * * user initiated processing on B node + * *) + * + * BEGIN + * group = FALSE; + * + * do name discovery on destination name, returns name type and + * IP address; + * + * IF name type is group name THEN + * BEGIN + * group = TRUE; + * END + * + * (* + * * build datagram service UDP packet; + * *) + * convert source and destination NetBIOS names into + * half-ASCII, biased encoded name; + * SOURCE_NAME = cat(source, SCOPE_ID); + * SOURCE_IP = this nodes IP address; + * SOURCE_PORT = DGM_SRVC_UDP_PORT; + * + * IF NetBIOS broadcast THEN + * BEGIN + * DESTINATION_NAME = cat("*", SCOPE_ID) + * END + * ELSE + * BEGIN + * DESTINATION_NAME = cat(destination, SCOPE_ID) + * END + * + * MSG_TYPE = select_one_from_set + * {BROADCAST, DIRECT_UNIQUE, DIRECT_GROUP} + * DGM_ID = next transaction id for Datagrams; + * DGM_LENGTH = length of data + length of second level encoded + * source and destination names; + * + * IF (length of the NetBIOS Datagram, including UDP and + * IP headers, > MAX_DATAGRAM_LENGTH) THEN + * BEGIN + * (* + * * fragment NetBIOS datagram into 2 UDP packets + * *) + * Put names into 1st UDP packet and any data that fits + * after names; + * Set MORE and FIRST bits in 1st UDP packets FLAGS; + * OFFSET in 1st UDP = 0; + * + * Replicate NetBIOS Datagram header from 1st UDP packet + * into 2nd UDP packet; + * Put rest of data in 2nd UDP packet; + * Clear MORE and FIRST bits in 2nd UDP packets FLAGS; + * OFFSET in 2nd UDP = DGM_LENGTH - number of name and + * data bytes in 1st UDP; + * END + * BEGIN + * (* + * * Only need one UDP packet + * *) + * USER_DATA = data; + * Clear MORE bit and set FIRST bit in FLAGS; + * OFFSET = 0; + * END + * + * IF (group == TRUE) OR (NetBIOS broadcast) THEN + * BEGIN + * send UDP packet(s) to BROADCAST_ADDRESS; + * END + * ELSE + * BEGIN + * send UDP packet(s) to IP address returned by name + * discovery; + * END + * END (* procedure *) + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MSG_TYPE | FLAGS | DGM_ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_IP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_PORT | DGM_LENGTH | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | PACKET_OFFSET | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * MSG_TYPE values (in hexidecimal): + * + * 10 - DIRECT_UNIQUE DATAGRAM + * 11 - DIRECT_GROUP DATAGRAM + * 12 - BROADCAST DATAGRAM + * 13 - DATAGRAM ERROR + * 14 - DATAGRAM QUERY REQUEST + * 15 - DATAGRAM POSITIVE QUERY RESPONSE + * 16 - DATAGRAM NEGATIVE QUERY RESPONSE + * + * Bit definitions of the FLAGS field: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 0 | SNT | F | M | + * +---+---+---+---+---+---+---+---+ + * + * Symbol Bit(s) Description + * + * M 7 MORE flag, If set then more NetBIOS datagram + * fragments follow. + * + * F 6 FIRST packet flag, If set then this is first + * (and possibly only) fragment of NetBIOS + * datagram + * + * SNT 4,5 Source End-Node type: + * 00 = B node + * 01 = P node + * 10 = M node + * 11 = NBDD + * RESERVED 0-3 Reserved, must be zero (0) + * (But MS sets bit 3 in this field) + * + */ + +int +smb_netbios_datagram_send(struct name_entry *src, struct name_entry *dest, + unsigned char *data, int length) +{ + uint32_t ipaddr; + size_t count, srclen, destlen, sinlen; + struct addr_entry *addr; + struct sockaddr_in sin; + char *buffer; + char ha_source[NETBIOS_DOMAIN_NAME_MAX]; + char ha_dest[NETBIOS_DOMAIN_NAME_MAX]; + net_cfg_t cfg; + + (void) smb_first_level_name_encode(src, (unsigned char *)ha_source, + sizeof (ha_source)); + srclen = strlen(ha_source) + 1; + + (void) smb_first_level_name_encode(dest, (unsigned char *)ha_dest, + sizeof (ha_dest)); + destlen = strlen(ha_dest) + 1; + + /* give some extra room */ + buffer = (char *)malloc(MAX_DATAGRAM_LENGTH * 4); + if (buffer == 0) { + syslog(LOG_ERR, "netbios: datagram send (resource shortage)"); + return (-1); + } + + buffer[0] = DATAGRAM_TYPE_DIRECT_UNIQUE; + switch (smb_node_type) { + case 'B': + buffer[1] = DATAGRAM_FLAGS_B_NODE | DATAGRAM_FLAGS_FIRST; + break; + case 'P': + buffer[1] = DATAGRAM_FLAGS_P_NODE | DATAGRAM_FLAGS_FIRST; + break; + case 'M': + buffer[1] = DATAGRAM_FLAGS_M_NODE | DATAGRAM_FLAGS_FIRST; + break; + case 'H': + default: + buffer[1] = DATAGRAM_FLAGS_H_NODE | DATAGRAM_FLAGS_FIRST; + break; + } + + datagram_id++; + BE_OUT16(&buffer[2], datagram_id); + (void) memcpy(&buffer[4], &src->addr_list.sin.sin_addr.s_addr, + sizeof (uint32_t)); + (void) memcpy(&buffer[8], &src->addr_list.sin.sin_port, + sizeof (uint16_t)); + BE_OUT16(&buffer[10], length + srclen + destlen); + BE_OUT16(&buffer[12], 0); + + bcopy(ha_source, &buffer[14], srclen); + bcopy(ha_dest, &buffer[14 + srclen], destlen); + bcopy(data, &buffer[14 + srclen + destlen], length); + count = &buffer[14 + srclen + destlen + length] - buffer; + + bzero(&sin, sizeof (sin)); + sin.sin_family = AF_INET; + sinlen = sizeof (sin); + addr = &dest->addr_list; + do { + ipaddr = addr->sin.sin_addr.s_addr; + /* Don't send anything to myself... */ + if (smb_nic_get_byip(ipaddr, &cfg) != NULL) { + goto next; + } + + sin.sin_addr.s_addr = ipaddr; + sin.sin_port = addr->sin.sin_port; + (void) sendto(datagram_sock, buffer, count, 0, + (struct sockaddr *)&sin, sinlen); + +next: addr = addr->forw; + } while (addr != &dest->addr_list); + free(buffer); + return (0); +} + + +int +smb_netbios_datagram_send_to_net(struct name_entry *src, + struct name_entry *dest, char *data, int length) +{ + uint32_t ipaddr; + size_t count, srclen, destlen, sinlen; + struct addr_entry *addr; + struct sockaddr_in sin; + char *buffer; + char ha_source[NETBIOS_DOMAIN_NAME_MAX]; + char ha_dest[NETBIOS_DOMAIN_NAME_MAX]; + net_cfg_t cfg; + + (void) smb_first_level_name_encode(src, (unsigned char *)ha_source, + sizeof (ha_source)); + srclen = strlen(ha_source) + 1; + + (void) smb_first_level_name_encode(dest, (unsigned char *)ha_dest, + sizeof (ha_dest)); + destlen = strlen(ha_dest) + 1; + + /* give some extra room */ + buffer = (char *)malloc(MAX_DATAGRAM_LENGTH * 4); + if (buffer == 0) { + syslog(LOG_ERR, "netbios: datagram send (resource shortage)"); + return (-1); + } + + buffer[0] = DATAGRAM_TYPE_DIRECT_UNIQUE; + switch (smb_node_type) { + case 'B': + buffer[1] = DATAGRAM_FLAGS_B_NODE | DATAGRAM_FLAGS_FIRST; + break; + case 'P': + buffer[1] = DATAGRAM_FLAGS_P_NODE | DATAGRAM_FLAGS_FIRST; + break; + case 'M': + buffer[1] = DATAGRAM_FLAGS_M_NODE | DATAGRAM_FLAGS_FIRST; + break; + case 'H': + default: + buffer[1] = DATAGRAM_FLAGS_H_NODE | DATAGRAM_FLAGS_FIRST; + break; + } + + datagram_id++; + BE_OUT16(&buffer[2], datagram_id); + (void) memcpy(&buffer[4], &src->addr_list.sin.sin_addr.s_addr, + sizeof (uint32_t)); + (void) memcpy(&buffer[8], &src->addr_list.sin.sin_port, + sizeof (uint16_t)); + BE_OUT16(&buffer[10], length + srclen + destlen); + BE_OUT16(&buffer[12], 0); + + bcopy(ha_source, &buffer[14], srclen); + bcopy(ha_dest, &buffer[14 + srclen], destlen); + bcopy(data, &buffer[14 + srclen + destlen], length); + count = &buffer[14 + srclen + destlen + length] - buffer; + + bzero(&sin, sizeof (sin)); + sin.sin_family = AF_INET; + sinlen = sizeof (sin); + addr = &dest->addr_list; + do { + ipaddr = addr->sin.sin_addr.s_addr; + if (smb_nic_get_byip(ipaddr, &cfg) != NULL) { + goto next; + } + sin.sin_addr.s_addr = ipaddr; + sin.sin_port = addr->sin.sin_port; + (void) sendto(datagram_sock, buffer, count, 0, + (struct sockaddr *)&sin, sinlen); + +next: addr = addr->forw; + } while (addr != &dest->addr_list); + free(buffer); + return (0); +} + + +int +smb_datagram_decode(struct datagram *datagram, int bytes) +{ + unsigned char *ha_src; + unsigned char *ha_dest; + unsigned char *data; + + if (bytes < DATAGRAM_HEADER_LENGTH) { + syslog(LOG_ERR, "NbtDatagramDecode[%d]: too small packet", + bytes); + return (-1); + } + + ha_src = &datagram->rawbuf[DATAGRAM_HEADER_LENGTH]; + ha_dest = ha_src + strlen((char *)ha_src) + 1; + data = ha_dest + strlen((char *)ha_dest) + 1; + + bzero(&datagram->src, sizeof (struct name_entry)); + bzero(&datagram->dest, sizeof (struct name_entry)); + + datagram->rawbytes = bytes; + datagram->packet_type = datagram->rawbuf[0]; + datagram->flags = datagram->rawbuf[1]; + datagram->datagram_id = BE_IN16(&datagram->rawbuf[2]); + + datagram->src.addr_list.sinlen = sizeof (struct sockaddr_in); + (void) memcpy(&datagram->src.addr_list.sin.sin_addr.s_addr, + &datagram->rawbuf[4], sizeof (uint32_t)); + (void) memcpy(&datagram->src.addr_list.sin.sin_port, + &datagram->rawbuf[8], sizeof (uint16_t)); + datagram->src.addr_list.forw = datagram->src.addr_list.back = + &datagram->src.addr_list; + + datagram->data = data; + datagram->data_length = BE_IN16(&datagram->rawbuf[10]); + datagram->offset = BE_IN16(&datagram->rawbuf[12]); + + if (smb_first_level_name_decode(ha_src, &datagram->src) < 0) { + syslog(LOG_DEBUG, "NbtDatagram[%s]: invalid calling name", + inet_ntoa(datagram->src.addr_list.sin.sin_addr)); + syslog(LOG_DEBUG, "Calling name: <%02X>%32.32s", + ha_src[0], &ha_src[1]); + } + + datagram->dest.addr_list.forw = datagram->dest.addr_list.back = + &datagram->dest.addr_list; + + if (smb_first_level_name_decode(ha_dest, &datagram->dest) < 0) { + syslog(LOG_DEBUG, "NbtDatagram[%s]: invalid called name", + inet_ntoa(datagram->src.addr_list.sin.sin_addr)); + syslog(LOG_DEBUG, "Called name: <%02X>%32.32s", ha_dest[0], + &ha_dest[1]); + } + + return (0); +} + + +/* + * Function: int smb_netbios_process_BPM_datagram(unsigned char *packet, + * struct addr_entry *addr) + * + * Description from rfc1002: + * + * 5.3.3. RECEPTION OF NetBIOS DATAGRAMS BY ALL NODES + * + * The following algorithm discards out of order NetBIOS Datagram + * fragments. An implementation which reassembles out of order + * NetBIOS Datagram fragments conforms to this specification. The + * fragment discard timer is initialized to the value FRAGMENT_TIMEOUT. + * This value should be user configurable. The default value is + * given in Section 6, "Defined Constants and Variables". + * + * PROCEDURE datagram_packet(packet) + * + * (* + * * processing initiated by datagram packet reception + * * on B, P and M nodes + * *) + * BEGIN + * (* + * * if this node is a P node, ignore + * * broadcast packets. + * *) + * + * IF this is a P node AND incoming packet is + * a broadcast packet THEN + * BEGIN + * discard packet; + * END + * + * CASE packet type OF + * + * DATAGRAM SERVICE: + * BEGIN + * IF FIRST bit in FLAGS is set THEN + * BEGIN + * IF MORE bit in FLAGS is set THEN + * BEGIN + * Save 1st UDP packet of the Datagram; + * Set this Datagrams fragment discard + * timer to FRAGMENT_TIMEOUT; + * return; + * END + * ELSE + * Datagram is composed of a single + * UDP packet; + * END + * ELSE + * BEGIN + * (* Have the second fragment of a Datagram *) + * + * Search for 1st fragment by source IP address + * and DGM_ID; + * IF found 1st fragment THEN + * Process both UDP packets; + * ELSE + * BEGIN + * discard 2nd fragment UDP packet; + * return; + * END + * END + * + * IF DESTINATION_NAME is '*' THEN + * BEGIN + * (* NetBIOS broadcast *) + * + * deliver USER_DATA from UDP packet(s) to all + * outstanding receive broadcast + * datagram requests; + * return; + * END + * ELSE + * BEGIN (* non-broadcast *) + * (* Datagram for Unique or Group Name *) + * + * IF DESTINATION_NAME is not present in the + * local name table THEN + * BEGIN + * (* destination not present *) + * build DATAGRAM ERROR packet, clear + * FIRST and MORE bit, put in + * this nodes IP and PORT, set + * ERROR_CODE; + * send DATAGRAM ERROR packet to + * source IP address and port + * of UDP; + * discard UDP packet(s); + * return; + * END + * ELSE + * BEGIN (* good *) + * (* + * * Replicate received NetBIOS datagram for + * * each recipient + * *) + * FOR EACH pending NetBIOS users receive + * datagram operation + * BEGIN + * IF source name of operation + * matches destination name + * of packet THEN + * BEGIN + * deliver USER_DATA from UDP + * packet(s); + * END + * END (* for each *) + * return; + * END (* good *) + * END (* non-broadcast *) + * END (* datagram service *) + * + * DATAGRAM ERROR: + * BEGIN + * (* + * * name service returned incorrect information + * *) + * + * inform local name service that incorrect + * information was provided; + * + * IF this is a P or M node THEN + * BEGIN + * (* + * * tell NetBIOS Name Server that it may + * * have given incorrect information + * *) + * + * send NAME RELEASE REQUEST with name + * and incorrect IP address to NetBIOS + * Name Server; + * END + * END (* datagram error *) + * + * END (* case *) + * END + */ + +static struct datagram * +smb_netbios_datagram_getq(struct datagram *datagram) +{ + struct datagram *prev = 0; + + (void) mutex_lock(&smb_dgq_mtx); + for (prev = smb_datagram_queue.forw; + prev != (struct datagram *)((uintptr_t)&smb_datagram_queue); + prev = prev->forw) { + if (prev->src.addr_list.sin.sin_addr.s_addr == + datagram->src.addr_list.sin.sin_addr.s_addr) { + /* Something waiting */ + QUEUE_CLIP(prev); + (void) mutex_unlock(&smb_dgq_mtx); + bcopy(datagram->data, &prev->data[prev->data_length], + datagram->data_length); + prev->data_length += datagram->data_length; + free(datagram); + return (prev); + } + } + (void) mutex_unlock(&smb_dgq_mtx); + + return (0); +} + +static void +smb_netbios_BPM_datagram(struct datagram *datagram) +{ + struct name_entry *entry = 0; + struct datagram *qpacket = 0; + pthread_t browser_dispatch; + + switch (datagram->packet_type) { + case DATAGRAM_TYPE_BROADCAST : + if (smb_node_type == 'P') { + /* + * if this node is a P node, ignore + * broadcast packets. + */ + break; + } + /* FALLTHROUGH */ + + case DATAGRAM_TYPE_DIRECT_UNIQUE : + case DATAGRAM_TYPE_DIRECT_GROUP : + if ((datagram->flags & DATAGRAM_FLAGS_FIRST) != 0) { + if (datagram->flags & DATAGRAM_FLAGS_MORE) { + /* Save 1st UDP packet of the Datagram */ + datagram->discard_timer = FRAGMENT_TIMEOUT; + (void) mutex_lock(&smb_dgq_mtx); + QUEUE_INSERT_TAIL(&smb_datagram_queue, datagram) + (void) mutex_unlock(&smb_dgq_mtx); + return; + } + /* process datagram */ + } else { + qpacket = smb_netbios_datagram_getq(datagram); + if (qpacket) { + datagram = qpacket; + goto process_datagram; + } + break; + } + +process_datagram: + entry = 0; + if ((strcmp((char *)datagram->dest.name, "*") == 0) || + ((entry = + smb_netbios_cache_lookup(&datagram->dest)) != 0)) { + if (entry) { + int is_local = IS_LOCAL(entry->attributes); + smb_netbios_cache_unlock_entry(entry); + + if (is_local) { + (void) pthread_create(&browser_dispatch, + 0, smb_browser_dispatch, + (void *)datagram); + (void) pthread_detach(browser_dispatch); + return; + } + } + + datagram->rawbuf[0] = DATAGRAM_TYPE_ERROR_DATAGRAM; + datagram->rawbuf[1] &= DATAGRAM_FLAGS_SRC_TYPE; + + (void) memcpy(&datagram->rawbuf[4], + &datagram->src.addr_list.sin.sin_addr.s_addr, + sizeof (uint32_t)); + BE_OUT16(&datagram->rawbuf[8], DGM_SRVC_UDP_PORT); + + (void) sendto(datagram_sock, datagram->rawbuf, + datagram->rawbytes, 0, + (struct sockaddr *)&datagram->src.addr_list.sin, + datagram->src.addr_list.sinlen); + } + break; + + case DATAGRAM_TYPE_ERROR_DATAGRAM : + break; + } + free(datagram); +} + + +/* + * smb_netbios_process_NBDD_datagram + * + * Description from rfc1002: + * + * + * 5.3.4. PROTOCOLS FOR THE NBDD + * + * The key to NetBIOS Datagram forwarding service is the packet + * delivered to the destination end node must have the same NetBIOS + * header as if the source end node sent the packet directly to the + * destination end node. Consequently, the NBDD does not reassemble + * NetBIOS Datagrams. It forwards the UDP packet as is. + * + * PROCEDURE datagram_packet(packet) + * + * (* + * * processing initiated by a incoming datagram service + * * packet on a NBDD node. + * *) + * + * BEGIN + * CASE packet type OF + * + * DATAGRAM SERVICE: + * BEGIN + * IF packet was sent as a directed + * NetBIOS datagram THEN + * BEGIN + * (* + * * provide group forwarding service + * * + * * Forward datagram to each member of the + * * group. Can forward via: + * * 1) get list of group members and send + * * the DATAGRAM SERVICE packet unicast + * * to each + * * 2) use Group Multicast, if available + * * 3) combination of 1) and 2) + * *) + * + * ... + * + * END + * + * ELSE + * BEGIN + * (* + * * provide broadcast forwarding service + * * + * * Forward datagram to every node in the + * * NetBIOS scope. Can forward via: + * * 1) get list of group members and send + * * the DATAGRAM SERVICE packet unicast + * * to each + * * 2) use Group Multicast, if available + * * 3) combination of 1) and 2) + * *) + * + * ... + * + * END + * END (* datagram service *) + * + * DATAGRAM ERROR: + * BEGIN + * (* + * * Should never receive these because Datagrams + * * forwarded have source end node IP address and + * * port in NetBIOS header. + * *) + * + * send DELETE NAME REQUEST with incorrect name and + * IP address to NetBIOS Name Server; + * + * END (* datagram error *) + * + * DATAGRAM QUERY REQUEST: + * BEGIN + * IF can send packet to DESTINATION_NAME THEN + * BEGIN + * (* + * * NBDD is able to relay Datagrams for + * * this name + * *) + * + * send POSITIVE DATAGRAM QUERY RESPONSE to + * REQUEST source IP address and UDP port + * with requests DGM_ID; + * END + * ELSE + * BEGIN + * (* + * * NBDD is NOT able to relay Datagrams for + * * this name + * *) + * + * send NEGATIVE DATAGRAM QUERY RESPONSE to + * REQUEST source IP address and UDP port + * + * with requests DGM_ID; + * END + * END (* datagram query request *) + * + * END (* case *) + * END (* procedure *) + */ + + +/* + * Function: int smb_netbios_datagram_service_daemon(void) + * + * Description: + * + * 4.4. DATAGRAM SERVICE PACKETS + * + * 4.4.1. NetBIOS DATAGRAM HEADER + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MSG_TYPE | FLAGS | DGM_ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_IP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOURCE_PORT | DGM_LENGTH | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | PACKET_OFFSET | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * MSG_TYPE values (in hexidecimal): + * + * 10 - DIRECT_UNIQUE DATAGRAM + * 11 - DIRECT_GROUP DATAGRAM + * 12 - BROADCAST DATAGRAM + * 13 - DATAGRAM ERROR + * 14 - DATAGRAM QUERY REQUEST + * 15 - DATAGRAM POSITIVE QUERY RESPONSE + * 16 - DATAGRAM NEGATIVE QUERY RESPONSE + * + * Bit definitions of the FLAGS field: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 0 | SNT | F | M | + * +---+---+---+---+---+---+---+---+ + * + * Symbol Bit(s) Description + * + * M 7 MORE flag, If set then more NetBIOS datagram + * fragments follow. + * + * F 6 FIRST packet flag, If set then this is first + * (and possibly only) fragment of NetBIOS + * datagram + * + * SNT 4,5 Source End-Node type: + * 00 = B node + * 01 = P node + * 10 = M node + * 11 = NBDD + * RESERVED 0-3 Reserved, must be zero (0) + * + * Inputs: + * Nothing + * + * Returns: + * int -> Description + */ + +/*ARGSUSED*/ +void * +smb_netbios_datagram_service_daemon(void *arg) +{ + struct sockaddr_in sin; + struct datagram *datagram; + int bytes, flag = 1; + net_cfg_t cfg; + + (void) mutex_lock(&smb_dgq_mtx); + bzero(&smb_datagram_queue, sizeof (smb_datagram_queue)); + smb_datagram_queue.forw = smb_datagram_queue.back = + (struct datagram *)((uintptr_t)&smb_datagram_queue); + (void) mutex_unlock(&smb_dgq_mtx); + + if ((datagram_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + syslog(LOG_ERR, + "smbd: Could not create AF_INET, SOCK_DGRAM, socket"); + smb_netbios_chg_status(NETBIOS_DATAGRAM_SVC_FAILED, 1); + return (0); + } + + bzero(&sin, sizeof (sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(DGM_SRVC_UDP_PORT); + if (bind(datagram_sock, (struct sockaddr *)&sin, sizeof (sin)) != 0) { + syslog(LOG_ERR, "smbd: Bind to name service port %d failed", + DGM_SRVC_UDP_PORT); + (void) close(datagram_sock); + smb_netbios_chg_status(NETBIOS_DATAGRAM_SVC_FAILED, 1); + return (0); + } + (void) setsockopt(datagram_sock, SOL_SOCKET, SO_BROADCAST, &flag, + sizeof (flag)); + + smb_netbios_chg_status(NETBIOS_DATAGRAM_SVC_RUNNING, 1); + + while (((nb_status.state & NETBIOS_SHUTTING_DOWN) == 0) || + (nb_status.state & NETBIOS_BROWSER_RUNNING)) { + if ((datagram = (struct datagram *) + malloc(sizeof (struct datagram))) == 0) { + /* Sleep for 10 sec and try again */ + (void) sleep(10); + continue; + } + +ignore: bzero(&datagram->inaddr, sizeof (struct addr_entry)); + datagram->inaddr.sinlen = sizeof (datagram->inaddr.sin); + datagram->inaddr.forw = datagram->inaddr.back = + &datagram->inaddr; + + if ((bytes = recvfrom(datagram_sock, datagram->rawbuf, + MAX_DATAGRAM_LENGTH, 0, + (struct sockaddr *)&datagram->inaddr.sin, + &datagram->inaddr.sinlen)) < 0) { + syslog(LOG_ERR, + "smbd: NETBIOS datagram - recvfrom failed"); + smb_netbios_chg_status(NETBIOS_DATAGRAM_SVC_FAILED, 1); + break; + } + + /* Ignore any incoming packets from myself... */ + if (smb_nic_get_byip( + datagram->inaddr.sin.sin_addr.s_addr, + &cfg) != NULL) { + goto ignore; + } + if (smb_datagram_decode(datagram, bytes) < 0) + goto ignore; + + /* + * This code was doing the wrong thing with responses from a + * Windows2000 PDC because both DATAGRAM_FLAGS_H_NODE and + * DATAGRAM_FLAGS_NBDD are defined to be the same value (see + * netbios.h). Since the Windows2000 PDC wants to be an H-Node, + * we need to handle all messages via smb_netbios_BPM_datagram. + * + * if ((datagram->flags & DATAGRAM_FLAGS_SRC_TYPE) == + * DATAGRAM_FLAGS_NBDD) + * smb_netbios_NBDD_datagram(datagram); + * else + * smb_netbios_BPM_datagram(datagram); + */ + + smb_netbios_BPM_datagram(datagram); + } + + smb_netbios_chg_status(NETBIOS_DATAGRAM_SVC_RUNNING, 0); + + (void) mutex_lock(&nb_status.mtx); + while (nb_status.state & NETBIOS_BROWSER_RUNNING) + (void) cond_wait(&nb_status.cv, &nb_status.mtx); + (void) mutex_unlock(&nb_status.mtx); + + (void) close(datagram_sock); + smb_netbios_datagram_fini(); + syslog(LOG_DEBUG, "smbd: Netbios Datagram Service is down\n"); + return (0); +} + +static char +/* LINTED - E_STATIC_UNUSED */ +nb_fmt_flags(unsigned char flags) +{ + switch (flags & DATAGRAM_FLAGS_SRC_TYPE) { + case DATAGRAM_FLAGS_B_NODE: return ('B'); + case DATAGRAM_FLAGS_P_NODE: return ('P'); + case DATAGRAM_FLAGS_M_NODE: return ('M'); + case DATAGRAM_FLAGS_H_NODE: return ('H'); + default: return ('?'); + } +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_name.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_name.c new file mode 100644 index 0000000000..2b2d20e3c3 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_name.c @@ -0,0 +1,4738 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Description: + * + * Contains base code for netbios name service. + * + * + * 6. DEFINED CONSTANTS AND VARIABLES + * + * GENERAL: + * + * SCOPE_ID The name of the NetBIOS scope. + * + * This is expressed as a character + * string meeting the requirements of + * the domain name system and without + * a leading or trailing "dot". + * + * An implementation may elect to make + * this a single global value for the + * node or allow it to be specified + * with each separate NetBIOS name + * (thus permitting cross-scope + * references.) + * + * BROADCAST_ADDRESS An IP address composed of the + * nodes's network and subnetwork + * numbers with all remaining bits set + * to one. + * + * I.e. "Specific subnet" broadcast + * addressing according to section 2.3 + * of RFC 950. + * + * BCAST_REQ_RETRY_TIMEOUT 250 milliseconds. + * An adaptive timer may be used. + * + * BCAST_REQ_RETRY_COUNT 3 + * + * UCAST_REQ_RETRY_TIMEOUT 5 seconds + * An adaptive timer may be used. + * + * UCAST_REQ_RETRY_COUNT 3 + * + * MAX_DATAGRAM_LENGTH 576 bytes (default) + * + * + * NAME SERVICE: + * + * REFRESH_TIMER Negotiated with NAME for each name. + * + * CONFLICT_TIMER 1 second + * Implementations may chose a longer + * value. + * + * + * NAME_SERVICE_TCP_PORT 137 (decimal) + * + * NAME_SERVICE_UDP_PORT 137 (decimal) + * + * INFINITE_TTL 0 + */ + +#include <unistd.h> +#include <syslog.h> +#include <stdlib.h> +#include <synch.h> +#include <errno.h> +#include <netdb.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <arpa/inet.h> +#include <net/if_arp.h> + +#include <smbsrv/libsmbns.h> +#include <smbns_netbios.h> + +#define NAME_HEADER_SIZE 12 + +typedef struct name_reply { + struct name_reply *forw; + struct name_reply *back; + struct name_packet *packet; + struct addr_entry *addr; + unsigned short name_trn_id; + unsigned short flags; +} name_reply; + +static struct name_reply reply_queue; +static mutex_t rq_mtx; + +static mutex_t reply_mtx; +static cond_t reply_cv; + +static name_queue_t delete_queue; +static name_queue_t refresh_queue; + +/* + * Flag to control whether or not NetBIOS name refresh requests + * are logged. Set to non-zero to enable logging. + */ + +static unsigned short netbios_name_transcation_id = 1; +static int name_sock = 0; + +static int bcast_num = 0; +static int nbns_num = 0; +static struct addr_entry smb_bcast_list[SMB_PI_MAX_NETWORKS]; +static struct addr_entry smb_nbns[SMB_PI_MAX_WINS]; + +static int smb_netbios_process_response(unsigned short, struct addr_entry *, + struct name_packet *, uint32_t); + +static int smb_send_name_service_packet(struct addr_entry *addr, + struct name_packet *packet); + +static int +smb_end_node_challenge(struct name_reply *reply_info) +{ + int rc; + uint32_t retry; + unsigned short tid; + struct resource_record *answer; + struct name_question question; + struct addr_entry *addr; + struct name_entry *destination; + struct name_packet packet; + struct timespec st; + + /* + * The response packet has in it the address of the presumed owner + * of the name. Challenge that owner. If owner either does not + * respond or indicates that he no longer owns the name, claim the + * name. Otherwise, the name cannot be claimed. + */ + + if ((answer = reply_info->packet->answer) == 0) + return (-1); + + destination = answer->name; + question.name = answer->name; + + packet.info = NAME_QUERY_REQUEST | NM_FLAGS_UNICAST; + packet.qdcount = 1; /* question entries */ + packet.question = &question; + packet.ancount = 0; /* answer recs */ + packet.answer = NULL; + packet.nscount = 0; /* authority recs */ + packet.authority = NULL; + packet.arcount = 0; /* additional recs */ + packet.additional = NULL; + + addr = &destination->addr_list; + for (retry = 0; retry < UCAST_REQ_RETRY_COUNT; retry++) { + tid = netbios_name_transcation_id++; + packet.name_trn_id = tid; + if (smb_send_name_service_packet(addr, &packet) >= 0) { + if ((rc = smb_netbios_process_response(tid, addr, + &packet, UCAST_REQ_RETRY_TIMEOUT)) != 0) + return (rc); + } + st.tv_sec = 0; + st.tv_nsec = (UCAST_REQ_RETRY_TIMEOUT * 1000000); + (void) nanosleep(&st, 0); + } + /* No reply */ + return (0); +} + + +static struct name_reply * +smb_name_get_reply(unsigned short tid, uint32_t timeout) +{ + unsigned short info; + struct resource_record *answer; + struct name_reply *reply; + uint32_t wait_time, to_save; /* in millisecond */ + struct timeval wt; + timestruc_t to; + + to_save = timeout; + reply = (struct name_reply *)malloc(sizeof (struct name_reply)); + if (reply != 0) { + reply->flags = 0; + reply->name_trn_id = tid; + (void) mutex_lock(&rq_mtx); + QUEUE_INSERT_TAIL(&reply_queue, reply); + (void) mutex_unlock(&rq_mtx); + + for (;;) { + (void) gettimeofday(&wt, 0); + wait_time = wt.tv_usec / 1000; + + (void) mutex_lock(&reply_mtx); + to.tv_sec = 0; + to.tv_nsec = timeout * 1000000; + (void) cond_reltimedwait(&reply_cv, &reply_mtx, &to); + (void) mutex_unlock(&reply_mtx); + + if (reply->flags != 0) { + info = reply->packet->info; + if (PACKET_TYPE(info) == WACK_RESPONSE) { + answer = reply->packet->answer; + wait_time = (answer) ? + TO_MILLISECONDS(answer->ttl) : + DEFAULT_TTL; + free(reply->addr); + free(reply->packet); + timeout = to_save + wait_time; + reply->flags = 0; + reply->name_trn_id = tid; + (void) mutex_lock(&rq_mtx); + QUEUE_INSERT_TAIL(&reply_queue, reply); + (void) mutex_unlock(&rq_mtx); + continue; + } + return (reply); + } + (void) gettimeofday(&wt, 0); + wait_time = (wt.tv_usec / 1000) - wait_time; + if (wait_time >= timeout) { + (void) mutex_lock(&rq_mtx); + QUEUE_CLIP(reply); + (void) mutex_unlock(&rq_mtx); + free(reply); + break; + } + timeout -= wait_time; + } + } + + return (0); +} + +static void +smb_reply_ready(struct name_packet *packet, struct addr_entry *addr) +{ + struct name_reply *reply; + struct resource_record *answer; + + (void) mutex_lock(&rq_mtx); + for (reply = reply_queue.forw; reply != &reply_queue; + reply = reply->forw) { + if (reply->name_trn_id == packet->name_trn_id) { + QUEUE_CLIP(reply); + (void) mutex_unlock(&rq_mtx); + + reply->addr = addr; + reply->packet = packet; + + (void) mutex_lock(&reply_mtx); + reply->flags |= 0x0001; /* reply ready */ + (void) cond_signal(&reply_cv); + (void) mutex_unlock(&reply_mtx); + + return; + } + } + (void) mutex_unlock(&rq_mtx); + + /* Presumably nobody is waiting any more... */ + free(addr); + + answer = packet->answer; + if (answer) + smb_netbios_name_freeaddrs(answer->name); + free(packet); +} + +static int +smb_netbios_process_response(unsigned short tid, struct addr_entry *addr, + struct name_packet *packet, uint32_t timeout) +{ + int rc = 0; + unsigned short info; + struct name_reply *reply; + struct resource_record *answer; + struct name_entry *name; + struct name_entry *entry; + struct name_question *question; + uint32_t ttl; + + if ((reply = smb_name_get_reply(tid, timeout)) == 0) { + return (0); /* No reply: retry */ + } + info = reply->packet->info; + answer = reply->packet->answer; + + /* response */ + switch (PACKET_TYPE(info)) { + case NAME_QUERY_RESPONSE: + if (POSITIVE_RESPONSE(info)) { + addr = &answer->name->addr_list; + do { + /* + * Make sure that remote name is not + * flagged local + */ + addr->attributes &= ~NAME_ATTR_LOCAL; + + addr->refresh_ttl = addr->ttl = + (answer && answer->ttl) ? + (answer->ttl >> 1) : + TO_SECONDS(DEFAULT_TTL); + addr = addr->forw; + } while (addr != &answer->name->addr_list); + smb_netbios_name_dump(answer->name); + (void) smb_netbios_cache_insert_list(answer->name); + rc = 1; + } else { + rc = -1; + } + break; + + case NAME_REGISTRATION_RESPONSE: + if (NEGATIVE_RESPONSE(info)) { + if (RCODE(info) == RCODE_CFT_ERR) { + if (answer == 0) { + rc = -RCODE(info); + break; + } + + name = answer->name; + entry = smb_netbios_cache_lookup(name); + if (entry) { + /* + * a name in the state "conflict + * detected" does not "logically" exist + * on that node. No further session + * will be accepted on that name. + * No datagrams can be sent against + * that name. + * Such an entry will not be used for + * purposes of processing incoming + * request packets. + * The only valid user NetBIOS operation + * against such a name is DELETE NAME. + */ + entry->attributes |= NAME_ATTR_CONFLICT; + syslog(LOG_DEBUG, + "NETBIOS Name conflict: %15.15s", + entry->name); + smb_netbios_cache_unlock_entry(entry); + } + } + rc = -RCODE(info); + break; + } + + /* + * name can be added: + * adjust refresh timeout value, + * TTL, for this name + */ + question = packet->question; + ttl = (answer && answer->ttl) ? answer->ttl >> 1 + : TO_SECONDS(DEFAULT_TTL); + if ((entry = smb_netbios_cache_lookup(question->name)) != 0) { + addr = &entry->addr_list; + do { + if ((addr->refresh_ttl == 0) || + (ttl < addr->refresh_ttl)) + addr->refresh_ttl = addr->ttl = ttl; + addr = addr->forw; + } while (addr != &entry->addr_list); + smb_netbios_cache_unlock_entry(entry); + } + + rc = 1; + break; + + case NAME_RELEASE_RESPONSE: + rc = 1; + break; + + case END_NODE_CHALLENGE_REGISTRATION_REQUEST: + /* + * The response packet has in it the + * address of the presumed owner of the + * name. Challenge that owner. If + * owner either does not respond or + * indicates that he no longer owns the + * name, claim the name. Otherwise, + * the name cannot be claimed. + */ + rc = smb_end_node_challenge(reply); + break; + + default: + rc = 0; + break; + } + + if (answer) + smb_netbios_name_freeaddrs(answer->name); + free(reply->addr); + free(reply->packet); + free(reply); + return (rc); /* retry */ +} + +/* + * smb_name_buf_from_packet + * + * Description: + * Convert a NetBIOS Name Server Packet Block (npb) + * into the bits and bytes destined for the wire. + * The "buf" is used as a heap. + * + * Inputs: + * char * buf -> Buffer, from the wire + * unsigned n_buf -> Length of 'buf' + * name_packet *npb -> Packet block, decode into + * unsigned n_npb -> Max bytes in 'npb' + * + * Returns: + * >0 -> Encode successful, value is length of packet in "buf" + * -1 -> Hard error, can not possibly encode + * -2 -> Need more memory in buf -- it's too small + */ + +static int +smb_name_buf_from_packet(unsigned char *buf, + int n_buf, + struct name_packet *npb) +{ + struct addr_entry *raddr; + unsigned char *heap = buf; + unsigned char *end_heap = heap + n_buf; + unsigned char *dnptrs[32]; + unsigned char comp_name_buf[MAX_NAME_LENGTH]; + unsigned int tmp; + int i, step; + + if (n_buf < NAME_HEADER_SIZE) + return (-1); /* no header, impossible */ + + dnptrs[0] = heap; + dnptrs[1] = 0; + + BE_OUT16(heap, npb->name_trn_id); + heap += 2; + + BE_OUT16(heap, npb->info); + heap += 2; + + BE_OUT16(heap, npb->qdcount); + heap += 2; + + BE_OUT16(heap, npb->ancount); + heap += 2; + + BE_OUT16(heap, npb->nscount); + heap += 2; + + BE_OUT16(heap, npb->arcount); + heap += 2; + + for (i = 0; i < npb->qdcount; i++) { + if ((heap + 34 + 4) > end_heap) + return (-2); + + (void) smb_first_level_name_encode(npb->question[i].name, + comp_name_buf, sizeof (comp_name_buf)); + (void) strcpy((char *)heap, (char *)comp_name_buf); + heap += strlen((char *)comp_name_buf) + 1; + + BE_OUT16(heap, npb->question[i].question_type); + heap += 2; + + BE_OUT16(heap, npb->question[i].question_class); + heap += 2; + } + + for (step = 1; step <= 3; step++) { + struct resource_record *nrr; + int n; + + /* truly ugly, but saves code copying */ + if (step == 1) { + n = npb->ancount; + nrr = npb->answer; + } else if (step == 2) { + n = npb->nscount; + nrr = npb->authority; + } else { /* step == 3 */ + n = npb->arcount; + nrr = npb->additional; + } + + for (i = 0; i < n; i++) { + if ((heap + 34 + 10) > end_heap) + return (-2); + + (void) smb_first_level_name_encode(nrr->name, + comp_name_buf, sizeof (comp_name_buf)); + (void) strcpy((char *)heap, (char *)comp_name_buf); + heap += strlen((char *)comp_name_buf) + 1; + + BE_OUT16(heap, nrr[i].rr_type); + heap += 2; + + BE_OUT16(heap, nrr[i].rr_class); + heap += 2; + + BE_OUT32(heap, nrr[i].ttl); + heap += 4; + + BE_OUT16(heap, nrr[i].rdlength); + heap += 2; + + if ((tmp = nrr[i].rdlength) > 0) { + if ((heap + tmp) > end_heap) + return (-2); + + if (nrr[i].rr_type == NAME_RR_TYPE_NB && + nrr[i].rr_class == NAME_RR_CLASS_IN && + tmp >= 6 && nrr[i].rdata == 0) { + tmp = nrr[i].name->attributes & + (NAME_ATTR_GROUP | + NAME_ATTR_OWNER_NODE_TYPE); + BE_OUT16(heap, tmp); + heap += 2; + + raddr = &nrr[i].name->addr_list; + (void) memcpy(heap, + &raddr->sin.sin_addr.s_addr, + sizeof (uint32_t)); + heap += 4; + } else { + bcopy(nrr[i].rdata, heap, tmp); + heap += tmp; + } + } + } + } + return (heap - buf); +} + +/* + * strnchr + * + * Lookup for character 'c' in first 'n' chars of string 's'. + * Returns pointer to the found char, otherwise returns 0. + */ +static char * +strnchr(const char *s, char c, int n) +{ + char *ps = (char *)s; + char *es = (char *)s + n; + + while (ps < es && *ps) { + if (*ps == c) + return (ps); + + ++ps; + } + + if (*ps == '\0' && c == '\0') + return (ps); + + return (0); +} + +/*ARGSUSED*/ +static int +is_multihome(char *name) +{ + return ((smb_nic_get_num() > 1) ? 1 : 0); +} + +/* + * smb_netbios_getname + * + * Get the Netbios name part of the given record. + * Does some boundary checks. + * + * Returns the name length on success, otherwise + * returns 0. + */ +static int +smb_netbios_getname(char *name, char *buf, char *buf_end) +{ + char *name_end; + int name_len; + + if (buf >= buf_end) { + /* no room for a NB name */ + return (0); + } + + name_end = strnchr(buf, '\0', buf_end - buf + 1); + if (name_end == 0) { + /* not a valid NB name */ + return (0); + } + + name_len = name_end - buf + 1; + + (void) strlcpy(name, buf, name_len); + return (name_len); +} + + +/* + * smb_name_buf_to_packet + * + * Description: + * Convert the bits and bytes that came from the wire + * into a NetBIOS Name Server Packet Block (npb). + * The "block" is used as a heap. + * + * Inputs: + * char * buf -> Buffer, from the wire + * int n_buf -> Length of 'buf' + * name_packet *npb -> Packet block, decode into + * int n_npb -> Max bytes in 'npb' + * + * Returns: + * >0 -> Decode (parse) successful, value is byte length of npb + * -1 -> Hard error, can not possibly decode + * -2 -> Need more memory in npb -- it's too small + */ + +static struct name_packet * +smb_name_buf_to_packet(char *buf, int n_buf) +{ + struct name_packet *npb; + unsigned char *heap; + unsigned char *scan = (unsigned char *)buf; + unsigned char *scan_end = scan + n_buf; + char name_buf[MAX_NAME_LENGTH]; + struct resource_record *nrr = 0; + int rc, i, n, nn, ns; + unsigned short name_trn_id, info; + unsigned short qdcount, ancount, nscount, arcount; + struct addr_entry *next; + int name_len; + + if (n_buf < NAME_HEADER_SIZE) { + /* truncated header */ + syslog(LOG_DEBUG, "SmbNBNS: packet is too short (%d)", + n_buf); + return (0); + } + + name_trn_id = BE_IN16(scan); scan += 2; + info = BE_IN16(scan); scan += 2; + qdcount = BE_IN16(scan); scan += 2; + ancount = BE_IN16(scan); scan += 2; + nscount = BE_IN16(scan); scan += 2; + arcount = BE_IN16(scan); scan += 2; + + ns = sizeof (struct name_entry); + n = n_buf + sizeof (struct name_packet) + + ((unsigned)qdcount * (sizeof (struct name_question) + ns)) + + ((unsigned)ancount * (sizeof (struct resource_record) + ns)) + + ((unsigned)nscount * (sizeof (struct resource_record) + ns)) + + ((unsigned)arcount * (sizeof (struct resource_record) + ns)); + + if ((npb = (struct name_packet *)malloc(n)) == 0) { + return (0); + } + bzero(npb, n); + + heap = npb->block_data; + npb->name_trn_id = name_trn_id; + npb->info = info; + npb->qdcount = qdcount; + npb->ancount = ancount; + npb->nscount = nscount; + npb->arcount = arcount; + + /* scan is in position for question entries */ + + /* + * Measure the space needed for the tables + */ + if (qdcount > 0) { + /* LINTED - E_BAD_PTR_CAST_ALIGN */ + npb->question = (struct name_question *)heap; + heap += qdcount * sizeof (struct name_question); + for (i = 0; i < qdcount; i++) { + /* LINTED - E_BAD_PTR_CAST_ALIGN */ + npb->question[i].name = (struct name_entry *)heap; + heap += sizeof (struct name_entry); + } + } + + /* LINTED - E_BAD_PTR_CAST_ALIGN */ + nrr = (struct resource_record *)heap; + + if (ancount > 0) { + /* LINTED - E_BAD_PTR_CAST_ALIGN */ + npb->answer = (struct resource_record *)heap; + heap += ancount * sizeof (struct resource_record); + } + + if (nscount > 0) { + /* LINTED - E_BAD_PTR_CAST_ALIGN */ + npb->authority = (struct resource_record *)heap; + heap += nscount * sizeof (struct resource_record); + } + + if (arcount > 0) { + /* LINTED - E_BAD_PTR_CAST_ALIGN */ + npb->additional = (struct resource_record *)heap; + heap += arcount * sizeof (struct resource_record); + } + + /* + * Populate each resource_record's .name field. + * Done as a second pass so that all resource records + * (answer, authority, additional) are consecutive via nrr[i]. + */ + for (i = 0; i < (ancount + nscount + arcount); i++) { + /* LINTED - E_BAD_PTR_CAST_ALIGN */ + nrr[i].name = (struct name_entry *)heap; + heap += sizeof (struct name_entry); + } + + + for (i = 0; i < npb->qdcount; i++) { + name_len = smb_netbios_getname(name_buf, (char *)scan, + (char *)scan_end); + if (name_len <= 0) { + free(npb); + return (0); + } + + smb_init_name_struct(NETBIOS_EMPTY_NAME, 0, 0, 0, 0, 0, 0, + npb->question[i].name); + rc = smb_first_level_name_decode((unsigned char *)name_buf, + npb->question[i].name); + if (rc < 0) { + /* Couldn't decode the question name */ + free(npb); + return (0); + } + + scan += name_len; + if (scan + 4 > scan_end) { + /* no room for Question Type(2) and Class(2) fields */ + free(npb); + return (0); + } + + npb->question[i].question_type = BE_IN16(scan); scan += 2; + npb->question[i].question_class = BE_IN16(scan); scan += 2; + } + + /* + * Cheat. Remaining sections are of the same resource_record + * format. Table space is consecutive. + */ + + for (i = 0; i < (ancount + nscount + arcount); i++) { + if (scan[0] == 0xc0) { + /* Namebuf is reused... */ + rc = 2; + } else { + name_len = smb_netbios_getname(name_buf, (char *)scan, + (char *)scan_end); + if (name_len <= 0) { + free(npb); + return (0); + } + rc = name_len; + } + scan += rc; + + if (scan + 10 > scan_end) { + /* + * no room for RR_TYPE (2), RR_CLASS (2), TTL (4) and + * RDLENGTH (2) fields. + */ + free(npb); + return (0); + } + + smb_init_name_struct(NETBIOS_EMPTY_NAME, 0, 0, 0, 0, 0, 0, + nrr[i].name); + if ((rc = smb_first_level_name_decode((unsigned char *)name_buf, + nrr[i].name)) < 0) { + free(npb); + return (0); + } + + nrr[i].rr_type = BE_IN16(scan); scan += 2; + nrr[i].rr_class = BE_IN16(scan); scan += 2; + nrr[i].ttl = BE_IN32(scan); scan += 4; + nrr[i].rdlength = BE_IN16(scan); scan += 2; + + if ((n = nrr[i].rdlength) > 0) { + if ((scan + n) > scan_end) { + /* no room for RDATA */ + free(npb); + return (0); + } + bcopy(scan, heap, n); + + nn = n; + if (nrr[i].rr_type == 0x0020 && + nrr[i].rr_class == 0x01 && n >= 6) { + while (nn) { + if (nn == 6) + next = &nrr[i].name->addr_list; + else { + next = (struct addr_entry *) + malloc( + sizeof (struct addr_entry)); + if (next == 0) { + /* not enough memory */ + free(npb); + return (0); + } + QUEUE_INSERT_TAIL( + &nrr[i].name->addr_list, + next); + } + nrr[i].name->attributes = + BE_IN16(scan); + next->sin.sin_family = AF_INET; + next->sinlen = sizeof (next->sin); + (void) memcpy( + &next->sin.sin_addr.s_addr, + scan + 2, sizeof (uint32_t)); + next->sin.sin_port = + htons(DGM_SRVC_UDP_PORT); + nn -= 6; + scan += 6; + } + } else { + nrr[i].rdata = heap; + scan += n; + } + heap += n; + } + } + return (npb); +} + + +/* + * smb_send_name_service_packet + * + * Description: + * + * Send out a name service packet to proper destination. + * + * Inputs: + * struct netbios_name *dest -> NETBIOS name of destination + * struct name_packet *packet -> Packet to send + * + * Returns: + * success -> >0 + * failure -> <=0 + */ + +static int +smb_send_name_service_packet(struct addr_entry *addr, + struct name_packet *packet) +{ + unsigned char buf[MAX_DATAGRAM_LENGTH]; + int len; + + if ((len = smb_name_buf_from_packet(buf, sizeof (buf), packet)) < 0) { + errno = EINVAL; + return (-1); + } + + return (sendto(name_sock, buf, len, MSG_EOR, + (struct sockaddr *)&addr->sin, addr->sinlen)); +} + +/* + * 4.2.1.1. HEADER + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID | OPCODE | NM_FLAGS | RCODE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | QDCOUNT | ANCOUNT | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NSCOUNT | ARCOUNT | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Field Description + * + * NAME_TRN_ID Transaction ID for Name Service Transaction. + * Requester places a unique value for each active + * transaction. Responder puts NAME_TRN_ID value + * from request packet in response packet. + * + * OPCODE Packet type code, see table below. + * + * NM_FLAGS Flags for operation, see table below. + * + * RCODE Result codes of request. Table of RCODE values + * for each response packet below. + * + * QDCOUNT Unsigned 16 bit integer specifying the number of + * entries in the question section of a Name + * + * Service packet. Always zero (0) for responses. + * Must be non-zero for all NetBIOS Name requests. + * + * ANCOUNT Unsigned 16 bit integer specifying the number of + * resource records in the answer section of a Name + * Service packet. + * + * NSCOUNT Unsigned 16 bit integer specifying the number of + * resource records in the authority section of a + * Name Service packet. + * + * ARCOUNT Unsigned 16 bit integer specifying the number of + * resource records in the additional records + * section of a Name Service packet. + * + * The OPCODE field is defined as: + * + * 0 1 2 3 4 + * +---+---+---+---+---+ + * | R | OPCODE | + * +---+---+---+---+---+ + * + * Symbol Bit(s) Description + * + * OPCODE 1-4 Operation specifier: + * 0 = query + * 5 = registration + * 6 = release + * 7 = WACK + * 8 = refresh + * + * R 0 RESPONSE flag: + * if bit == 0 then request packet + * if bit == 1 then response packet. + */ + + +/* + * The NM_FLAGS field is defined as: + * + * + * 0 1 2 3 4 5 6 + * +---+---+---+---+---+---+---+ + * |AA |TC |RD |RA | 0 | 0 | B | + * +---+---+---+---+---+---+---+ + * + * Symbol Bit(s) Description + * + * B 6 Broadcast Flag. + * = 1: packet was broadcast or multicast + * = 0: unicast + * + * RA 3 Recursion Available Flag. + * + * Only valid in responses from a NetBIOS Name + * Server -- must be zero in all other + * responses. + * + * If one (1) then the NAME supports recursive + * query, registration, and release. + * + * If zero (0) then the end-node must iterate + * for query and challenge for registration. + * + * RD 2 Recursion Desired Flag. + * + * May only be set on a request to a NetBIOS + * Name Server. + * + * The NAME will copy its state into the + * response packet. + * + * If one (1) the NAME will iterate on the + * query, registration, or release. + * + * TC 1 Truncation Flag. + * + * Set if this message was truncated because the + * datagram carrying it would be greater than + * 576 bytes in length. Use TCP to get the + * information from the NetBIOS Name Server. + * + * AA 0 Authoritative Answer flag. + * + * Must be zero (0) if R flag of OPCODE is zero + * (0). + * + * If R flag is one (1) then if AA is one (1) + * then the node responding is an authority for + * the domain name. + * + * End nodes responding to queries always set + * this bit in responses. + * + */ + +/* + * 4.2.1.2. QUESTION SECTION + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | QUESTION_TYPE | QUESTION_CLASS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Field Description + * + * QUESTION_NAME The compressed name representation of the + * NetBIOS name for the request. + * + * QUESTION_TYPE The type of request. The values for this field + * are specified for each request. + * + * QUESTION_CLASS The class of the request. The values for this + * field are specified for each request. + * + * QUESTION_TYPE is defined as: + * + * Symbol Value Description: + * + * NB 0x0020 NetBIOS general Name Service Resource Record + * NBSTAT 0x0021 NetBIOS NODE STATUS Resource Record (See NODE + * STATUS REQUEST) + * + * QUESTION_CLASS is defined as: + * + * Symbol Value Description: + * + * IN 0x0001 Internet class + */ + +#define QUESTION_TYPE_NETBIOS_GENERAL 0x20 +#define QUESTION_TYPE_NETBIOS_STATUS 0x21 + +#define QUESTION_CLASS_INTERNET 0x0001 + +/* + * + * 4.2.1.3. RESOURCE RECORD + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RR_TYPE | RR_CLASS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RDLENGTH | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * / / + * / RDATA / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Field Description + * + * RR_NAME The compressed name representation of the + * NetBIOS name corresponding to this resource + * record. + * + * RR_TYPE Resource record type code + * + * RR_CLASS Resource record class code + * + * TTL The Time To Live of a the resource record's + * name. + * + * RDLENGTH Unsigned 16 bit integer that specifies the + * number of bytes in the RDATA field. + * + * RDATA RR_CLASS and RR_TYPE dependent field. Contains + * the resource information for the NetBIOS name. + * + * RESOURCE RECORD RR_TYPE field definitions: + * + * Symbol Value Description: + * + * A 0x0001 IP address Resource Record (See REDIRECT NAME + * QUERY RESPONSE) + * NS 0x0002 Name Server Resource Record (See REDIRECT + * NAME QUERY RESPONSE) + * NULL 0x000A NULL Resource Record (See WAIT FOR + * ACKNOWLEDGEMENT RESPONSE) + * NB 0x0020 NetBIOS general Name Service Resource Record + * (See NB_FLAGS and NB_ADDRESS, below) + * NBSTAT 0x0021 NetBIOS NODE STATUS Resource Record (See NODE + * STATUS RESPONSE) + */ + +#define RR_TYPE_IP_ADDRESS_RESOURCE 0x0001 +#define RR_TYPE_NAME_SERVER_RESOURCE 0x0002 +#define RR_TYPE_NULL_RESOURCE 0x000A +#define RR_TYPE_NETBIOS_RESOURCE 0x0020 +#define RR_TYPE_NETBIOS_STATUS 0x0021 + +/* + * + * RESOURCE RECORD RR_CLASS field definitions: + * + * Symbol Value Description: + * + * IN 0x0001 Internet class + */ +#define RR_CLASS_INTERNET_CLASS 0x0001 + +/* + * + * NB_FLAGS field of the RESOURCE RECORD RDATA field for RR_TYPE of + * "NB": + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | G | ONT | RESERVED | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * Symbol Bit(s) Description: + * + * RESERVED 3-15 Reserved for future use. Must be zero (0). + * ONT 1,2 Owner Node Type: + * 00 = B node + * 01 = P node + * 10 = M node + * 11 = Reserved for future use + * For registration requests this is the + * claimant's type. + * For responses this is the actual owner's + * type. + * + * G 0 Group Name Flag. + * If one (1) then the RR_NAME is a GROUP + * NetBIOS name. + * If zero (0) then the RR_NAME is a UNIQUE + * NetBIOS name. + * + * The NB_ADDRESS field of the RESOURCE RECORD RDATA field for + * RR_TYPE of "NB" is the IP address of the name's owner. + * + */ +#define RR_FLAGS_NB_ONT_MASK 0x6000 +#define RR_FLAGS_NB_ONT_B_NODE 0x0000 +#define RR_FLAGS_NB_ONT_P_NODE 0x2000 +#define RR_FLAGS_NB_ONT_M_NODE 0x4000 +#define RR_FLAGS_NB_ONT_RESERVED 0x6000 + +#define RR_FLAGS_NB_GROUP_NAME 0x8000 + +/* + * smb_netbios_send_rcv + * + * This function sends the given NetBIOS packet to the given + * address and get back the response. If send operation is not + * successful, it's repeated 'retries' times. + * + * Returns: + * 0 Unsuccessful send operation; no reply + * 1 Got reply + */ +static int +smb_netbios_send_rcv(int bcast, struct addr_entry *destination, + struct name_packet *packet, + uint32_t retries, uint32_t timeout) +{ + uint32_t retry; + unsigned short tid; + struct timespec st; + int rc; + + for (retry = 0; retry < retries; retry++) { + if ((destination->flags & ADDR_FLAG_VALID) == 0) + return (0); + + tid = netbios_name_transcation_id++; + packet->name_trn_id = tid; + if (smb_send_name_service_packet(destination, packet) >= 0) { + rc = smb_netbios_process_response(tid, destination, + packet, timeout); + + if ((rc > 0) || (bcast == BROADCAST)) + return (1); + + if (rc != 0) + return (0); + } + + st.tv_sec = 0; + st.tv_nsec = (timeout * 1000000); + (void) nanosleep(&st, 0); + } + + return (0); +} + +/* + * 4.2.2. NAME REGISTRATION REQUEST + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x5 |0|0|1|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Since the RR_NAME is the same name as the QUESTION_NAME, the + * RR_NAME representation must use pointers to the QUESTION_NAME + * name's labels to guarantee the length of the datagram is less + * than the maximum 576 bytes. See section above on name formats + * and also page 31 and 32 of RFC 883, Domain Names - Implementation + * and Specification, for a complete description of compressed name + * label pointers. + */ +static int +smb_send_name_registration_request(int bcast, struct name_question *question, + struct resource_record *additional) +{ + int gotreply = 0; + uint32_t retries; + uint32_t timeout; + struct addr_entry *destination; + struct name_packet packet; + unsigned char type; + int i, addr_num, rc; + + type = question->name->name[15]; + if ((type != 0x00) && (type != 0x20)) { + syslog(LOG_ERR, "netbios: error trying to register" + " non-local name"); + smb_netbios_name_logf(question->name); + question->name->attributes &= ~NAME_ATTR_LOCAL; + return (-1); + } + + if (bcast == BROADCAST) { + if (bcast_num == 0) + return (0); + destination = smb_bcast_list; + addr_num = bcast_num; + retries = BCAST_REQ_RETRY_COUNT; + timeout = BCAST_REQ_RETRY_TIMEOUT; + packet.info = NAME_REGISTRATION_REQUEST | NM_FLAGS_BROADCAST; + } else { + if (nbns_num == 0) + return (0); + destination = smb_nbns; + addr_num = nbns_num; + retries = UCAST_REQ_RETRY_COUNT; + timeout = UCAST_REQ_RETRY_TIMEOUT; + packet.info = NAME_REGISTRATION_REQUEST | NM_FLAGS_UNICAST; + } + + packet.qdcount = 1; /* question entries */ + packet.question = question; + packet.ancount = 0; /* answer recs */ + packet.answer = NULL; + packet.nscount = 0; /* authority recs */ + packet.authority = NULL; + packet.arcount = 1; /* additional recs */ + packet.additional = additional; + + if (IS_UNIQUE(question->name->attributes) && + (is_multihome((char *)(question->name->name)))) + packet.info |= NAME_MULTIHOME_REGISTRATION_REQUEST; + + for (i = 0; i < addr_num; i++) { + /* + * Only register with the Primary WINS server, + * unless we got no reply. + */ + if ((bcast == UNICAST) && gotreply) + break; + + rc = smb_netbios_send_rcv(bcast, &destination[i], &packet, + retries, timeout); + if (rc == 1) + gotreply = 1; + } + + return (gotreply); +} + +/* + * + * 4.2.4. NAME REFRESH REQUEST + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x8 |0|0|0|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +/*ARGSUSED*/ +static int +smb_send_name_refresh_request(int bcast, struct name_question *question, + struct resource_record *additional, int force) +{ + int rc = 0; + int gotreply = 0; + uint32_t retries; + uint32_t timeout; + struct addr_entry *addr; + struct addr_entry *destination; + struct name_packet packet; + unsigned char type; + int i, addr_num, q_addrs = 0; + + type = question->name->name[15]; + if ((type != 0x00) && (type != 0x20)) { + syslog(LOG_ERR, "attempt to refresh non-local name"); + smb_netbios_name_logf(question->name); + question->name->attributes &= ~NAME_ATTR_LOCAL; + return (-1); + } + switch (bcast) { + case BROADCAST : + if (bcast_num == 0) + return (-1); + destination = smb_bcast_list; + addr_num = bcast_num; + retries = BCAST_REQ_RETRY_COUNT; + timeout = BCAST_REQ_RETRY_TIMEOUT; + packet.info = NAME_REFRESH_REQUEST | NM_FLAGS_BROADCAST; + break; + + case UNICAST : + if (nbns_num == 0) + return (-1); + destination = smb_nbns; + addr_num = nbns_num; + retries = UCAST_REQ_RETRY_COUNT; + timeout = UCAST_REQ_RETRY_TIMEOUT; + packet.info = NAME_REFRESH_REQUEST | NM_FLAGS_UNICAST; + break; + + default: + destination = &question->name->addr_list; + /* + * the value of addr_num is irrelvant here, because + * the code is going to do special_process so it doesn't + * need the addr_num. We set a value here just to avoid + * compiler warning. + */ + addr_num = 0; + retries = UCAST_REQ_RETRY_COUNT; + timeout = UCAST_REQ_RETRY_TIMEOUT; + packet.info = NAME_REFRESH_REQUEST | NM_FLAGS_UNICAST; + q_addrs = 1; + break; + } + + if (IS_UNIQUE(question->name->attributes) && + (is_multihome((char *)(question->name->name)))) + packet.info |= NAME_MULTIHOME_REGISTRATION_REQUEST; + + packet.qdcount = 1; /* question entries */ + packet.question = question; + packet.ancount = 0; /* answer recs */ + packet.answer = NULL; + packet.nscount = 0; /* authority recs */ + packet.authority = NULL; + packet.arcount = 1; /* additional recs */ + packet.additional = additional; + + if (q_addrs) + goto special_process; + + for (i = 0; i < addr_num; i++) { + rc = smb_netbios_send_rcv(bcast, &destination[i], &packet, + retries, timeout); + if (rc == 1) + gotreply = 1; + } + + return (gotreply); + +special_process: + addr = destination; + do { + rc = smb_netbios_send_rcv(bcast, addr, &packet, + retries, timeout); + if (rc == 1) + gotreply = 1; + addr = addr->forw; + } while (addr != destination); + + return (gotreply); +} + +/* + * 4.2.5. POSITIVE NAME REGISTRATION RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x5 |1|0|1|1|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * + * + * 4.2.6. NEGATIVE NAME REGISTRATION RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x5 |1|0|1|1|0 0|0| RCODE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * RCODE field values: + * + * Symbol Value Description: + * + * FMT_ERR 0x1 Format Error. Request was invalidly + * formatted. + * SRV_ERR 0x2 Server failure. Problem with NAME, cannot + * process name. + * IMP_ERR 0x4 Unsupported request error. Allowable only + * for challenging NAME when gets an Update type + * registration request. + * RFS_ERR 0x5 Refused error. For policy reasons server + * will not register this name from this host. + * ACT_ERR 0x6 Active error. Name is owned by another node. + * CFT_ERR 0x7 Name in conflict error. A UNIQUE name is + * owned by more than one node. + */ +static int +smb_send_name_registration_response(struct addr_entry *addr, + struct name_packet *original_packet, unsigned short rcode) +{ + struct name_packet packet; + struct resource_record answer; + + bzero(&packet, sizeof (struct name_packet)); + bzero(&answer, sizeof (struct resource_record)); + + packet.name_trn_id = original_packet->name_trn_id; + packet.info = NAME_REGISTRATION_RESPONSE | NAME_NM_FLAGS_RA | + (rcode & NAME_RCODE_MASK); + packet.qdcount = 0; /* question entries */ + packet.question = NULL; + packet.ancount = 1; /* answer recs */ + packet.answer = &answer; + packet.nscount = 0; /* authority recs */ + packet.authority = NULL; + packet.arcount = 0; /* additional recs */ + packet.additional = NULL; + + answer.name = original_packet->question->name; + answer.rr_type = NAME_QUESTION_TYPE_NB; + answer.rr_class = NAME_QUESTION_CLASS_IN; + answer.ttl = original_packet->additional->ttl; + answer.rdlength = original_packet->additional->rdlength; + answer.rdata = original_packet->additional->rdata; + + return (smb_send_name_service_packet(addr, &packet)); +} + +/* + * 4.2.9. NAME RELEASE REQUEST & DEMAND + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x6 |0|0|0|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x00000000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Since the RR_NAME is the same name as the QUESTION_NAME, the + * RR_NAME representation must use label string pointers to the + * QUESTION_NAME labels to guarantee the length of the datagram is + * less than the maximum 576 bytes. This is the same condition as + * with the NAME REGISTRATION REQUEST. + */ +static int +smb_send_name_release_request_and_demand(int bcast, + struct name_question *question, struct resource_record *additional) +{ + int gotreply = 0; + int i, rc; + int addr_num; + uint32_t retries; + uint32_t timeout; + struct addr_entry *destination; + struct name_packet packet; + + if (bcast == BROADCAST) { + if (bcast_num == 0) + return (-1); + destination = smb_bcast_list; + addr_num = bcast_num; + retries = 1; /* BCAST_REQ_RETRY_COUNT */ + timeout = 100; /* BCAST_REQ_RETRY_TIMEOUT */ + packet.info = NAME_RELEASE_REQUEST | NM_FLAGS_BROADCAST; + } else { + if (nbns_num == 0) + return (-1); + destination = smb_nbns; + addr_num = nbns_num; + retries = 1; /* UCAST_REQ_RETRY_COUNT */ + timeout = 100; /* UCAST_REQ_RETRY_TIMEOUT */ + packet.info = NAME_RELEASE_REQUEST | NM_FLAGS_UNICAST; + } + + packet.qdcount = 1; /* question entries */ + packet.question = question; + packet.ancount = 0; /* answer recs */ + packet.answer = NULL; + packet.nscount = 0; /* authority recs */ + packet.authority = NULL; + packet.arcount = 1; /* additional recs */ + packet.additional = additional; + + for (i = 0; i < addr_num; i++) { + rc = smb_netbios_send_rcv(bcast, &destination[i], &packet, + retries, timeout); + if (rc == 1) + gotreply = 1; + } + + return (gotreply); +} + +/* + * 4.2.10. POSITIVE NAME RELEASE RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x6 |1|0|0|0|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * + * 4.2.11. NEGATIVE NAME RELEASE RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x6 |1|0|0|0|0 0|0| RCODE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0006 | NB_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * RCODE field values: + * + * Symbol Value Description: + * + * FMT_ERR 0x1 Format Error. Request was invalidly + * formatted. + * + * SRV_ERR 0x2 Server failure. Problem with NAME, cannot + * process name. + * + * RFS_ERR 0x5 Refused error. For policy reasons server + * will not release this name from this host. + * + * ACT_ERR 0x6 Active error. Name is owned by another node. + * Only that node may release it. A NetBIOS + * Name Server can optionally allow a node to + * release a name it does not own. This would + * facilitate detection of inactive names for + * nodes that went down silently. + */ +static int +/* LINTED - E_STATIC_UNUSED */ +smb_send_name_release_response(struct addr_entry *addr, + struct name_packet *original_packet, unsigned short rcode) +{ + struct name_packet packet; + struct resource_record answer; + + bzero(&packet, sizeof (struct name_packet)); + bzero(&answer, sizeof (struct resource_record)); + + packet.name_trn_id = original_packet->name_trn_id; + packet.info = NAME_RELEASE_RESPONSE | (rcode & NAME_RCODE_MASK); + packet.qdcount = 0; /* question entries */ + packet.question = NULL; + packet.ancount = 1; /* answer recs */ + packet.answer = &answer; + packet.nscount = 0; /* authority recs */ + packet.authority = NULL; + packet.arcount = 0; /* additional recs */ + packet.additional = NULL; + + answer.name = original_packet->question->name; + answer.rr_type = NAME_QUESTION_TYPE_NB; + answer.rr_class = NAME_QUESTION_CLASS_IN; + answer.ttl = original_packet->additional->ttl; + answer.rdlength = original_packet->additional->rdlength; + answer.rdata = original_packet->additional->rdata; + + return (smb_send_name_service_packet(addr, &packet)); +} + +/* + * + * 4.2.12. NAME QUERY REQUEST + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |0| 0x0 |0|0|1|0|0 0|B| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0001 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / QUESTION_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +static int +smb_send_name_query_request(int bcast, struct name_question *question) +{ + int rc = 0; + uint32_t retry, retries; + uint32_t timeout; + unsigned short tid; + struct addr_entry *destination; + struct name_packet packet; + int i, addr_num; + struct timespec st; + + if (bcast == BROADCAST) { + if (bcast_num == 0) + return (-1); + destination = smb_bcast_list; + addr_num = bcast_num; + retries = BCAST_REQ_RETRY_COUNT; + timeout = BCAST_REQ_RETRY_TIMEOUT; + packet.info = NAME_QUERY_REQUEST | NM_FLAGS_BROADCAST; + } else { + if (nbns_num == 0) + return (-1); + destination = smb_nbns; + addr_num = nbns_num; + retries = UCAST_REQ_RETRY_COUNT; + timeout = UCAST_REQ_RETRY_TIMEOUT; + packet.info = NAME_QUERY_REQUEST | NM_FLAGS_UNICAST; + } + packet.qdcount = 1; /* question entries */ + packet.question = question; + packet.ancount = 0; /* answer recs */ + packet.answer = NULL; + packet.nscount = 0; /* authority recs */ + packet.authority = NULL; + packet.arcount = 0; /* additional recs */ + packet.additional = NULL; + + for (i = 0; i < addr_num; i++) { + for (retry = 0; retry < retries; retry++) { + if ((destination->flags & ADDR_FLAG_VALID) == 0) + break; + tid = netbios_name_transcation_id++; + packet.name_trn_id = tid; + + if (smb_send_name_service_packet(&destination[i], + &packet) >= 0) { + if ((rc = smb_netbios_process_response(tid, + &destination[i], + &packet, timeout)) != 0) + break; + } + st.tv_sec = 0; + st.tv_nsec = (timeout * 1000000); + (void) nanosleep(&st, 0); + } + } + + return (rc); +} + + +/* + * + * 4.2.13. POSITIVE NAME QUERY RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x0 |1|T|1|?|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB (0x0020) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | TTL | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RDLENGTH | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | + * / ADDR_ENTRY ARRAY / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The ADDR_ENTRY ARRAY a sequence of zero or more ADDR_ENTRY + * records. Each ADDR_ENTRY record represents an owner of a name. + * For group names there may be multiple entries. However, the list + * may be incomplete due to packet size limitations. Bit 22, "T", + * will be set to indicate truncated data. + * + * Each ADDR_ENTRY has the following format: + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_FLAGS | NB_ADDRESS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NB_ADDRESS (continued) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * + * + * 4.2.14. NEGATIVE NAME QUERY RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x0 |1|0|1|?|0 0|0| RCODE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * / / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NULL (0x000A) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x00000000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * RCODE field values: + * + * Symbol Value Description + * + * FMT_ERR 0x1 Format Error. Request was invalidly + * formatted. + * SRV_ERR 0x2 Server failure. Problem with NAME, cannot + * process name. + * NAM_ERR 0x3 Name Error. The name requested does not + * exist. + * IMP_ERR 0x4 Unsupported request error. Allowable only + * for challenging NAME when gets an Update type + * registration request. + * RFS_ERR 0x5 Refused error. For policy reasons server + * will not register this name from this host. + */ +static int +smb_send_name_query_response(struct addr_entry *addr, + struct name_packet *original_packet, struct name_entry *entry, + unsigned short rcode) +{ + struct addr_entry *raddr; + struct name_packet packet; + struct resource_record answer; + unsigned short attr; + unsigned char data[MAX_DATAGRAM_LENGTH]; + unsigned char *scan = data; + + packet.name_trn_id = original_packet->name_trn_id; + packet.info = NAME_QUERY_RESPONSE | (rcode & NAME_RCODE_MASK); + packet.qdcount = 0; /* question entries */ + packet.question = NULL; + packet.ancount = 1; /* answer recs */ + packet.answer = &answer; + packet.nscount = 0; /* authority recs */ + packet.authority = NULL; + packet.arcount = 0; /* additional recs */ + packet.additional = NULL; + + answer.name = entry; + answer.rr_class = NAME_QUESTION_CLASS_IN; + answer.ttl = entry->addr_list.ttl; + answer.rdata = data; + if (rcode) { + answer.rr_type = NAME_RR_TYPE_NULL; + answer.rdlength = 0; + bzero(data, 6); + } else { + answer.rdlength = 0; + answer.rr_type = NAME_QUESTION_TYPE_NB; + raddr = &entry->addr_list; + scan = data; + do { + attr = entry->attributes & (NAME_ATTR_GROUP | + NAME_ATTR_OWNER_NODE_TYPE); + + BE_OUT16(scan, attr); scan += 2; + (void) memcpy(scan, &raddr->sin.sin_addr.s_addr, + sizeof (uint32_t)); + scan += 4; + + answer.rdlength += 6; + raddr = raddr->forw; + } while (raddr != &entry->addr_list); + } + + return (smb_send_name_service_packet(addr, &packet)); +} + +/* + * 4.2.18. NODE STATUS RESPONSE + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_TRN_ID |1| 0x0 |1|0|0|0|0 0|0| 0x0 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0001 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x0000 | 0x0000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * / RR_NAME / + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NBSTAT (0x0021) | IN (0x0001) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x00000000 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RDLENGTH | NUM_NAMES | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + * | | + * + + + * / NODE_NAME ARRAY / + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * / STATISTICS / + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The NODE_NAME ARRAY is an array of zero or more NUM_NAMES entries + * of NODE_NAME records. Each NODE_NAME entry represents an active + * name in the same NetBIOS scope as the requesting name in the + * local name table of the responder. RR_NAME is the requesting + * name. + * + * NODE_NAME Entry: + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * +--- ---+ + * | | + * +--- NETBIOS FORMAT NAME ---+ + * | | + * +--- ---+ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NAME_FLAGS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The NAME_FLAGS field: + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | G | ONT |DRG|CNF|ACT|PRM| RESERVED | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * The NAME_FLAGS field is defined as: + * + * Symbol Bit(s) Description: + * + * RESERVED 7-15 Reserved for future use. Must be zero (0). + * PRM 6 Permanent Name Flag. If one (1) then entry + * is for the permanent node name. Flag is zero + * (0) for all other names. + * ACT 5 Active Name Flag. All entries have this flag + * set to one (1). + * CNF 4 Conflict Flag. If one (1) then name on this + * node is in conflict. + * DRG 3 Deregister Flag. If one (1) then this name + * is in the process of being deleted. + * ONT 1,2 Owner Node Type: + * 00 = B node + * 01 = P node + * 10 = M node + * 11 = Reserved for future use + * G 0 Group Name Flag. + * If one (1) then the name is a GROUP NetBIOS + * name. + * If zero (0) then it is a UNIQUE NetBIOS name. + */ +#define NAME_FLAGS_PERMANENT_NAME 0x0200 +#define NAME_FLAGS_ACTIVE_NAME 0x0400 +#define NAME_FLAGS_CONFLICT 0x0800 +#define NAME_FLAGS_DEREGISTER 0x1000 +#define NAME_FLAGS_ONT_MASK 0x6000 +#define NAME_FLAGS_ONT_B_NODE 0x0000 +#define NAME_FLAGS_ONT_P_NODE 0x2000 +#define NAME_FLAGS_ONT_M_NODE 0x4000 +#define NAME_FLAGS_ONT_RESERVED 0x6000 +#define NAME_FLAGS_GROUP_NAME 0x8000 + + +/* + * STATISTICS Field of the NODE STATUS RESPONSE: + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | UNIT_ID (Unique unit ID) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | UNIT_ID,continued | JUMPERS | TEST_RESULT | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | VERSION_NUMBER | PERIOD_OF_STATISTICS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_OF_CRCs | NUMBER_ALIGNMENT_ERRORS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_OF_COLLISIONS | NUMBER_SEND_ABORTS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_GOOD_SENDS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_GOOD_RECEIVES | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_RETRANSMITS | NUMBER_NO_RESOURCE_CONDITIONS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NUMBER_FREE_COMMAND_BLOCKS | TOTAL_NUMBER_COMMAND_BLOCKS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |MAX_TOTAL_NUMBER_COMMAND_BLOCKS| NUMBER_PENDING_SESSIONS | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MAX_NUMBER_PENDING_SESSIONS | MAX_TOTAL_SESSIONS_POSSIBLE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SESSION_DATA_PACKET_SIZE | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +#define MAX_NETBIOS_REPLY_DATA_SIZE 500 + +static int +smb_send_node_status_response(struct addr_entry *addr, + struct name_packet *original_packet) +{ + uint32_t net_ipaddr, max_connections; + struct arpreq arpreq; + struct name_packet packet; + struct resource_record answer; + unsigned char *scan; + unsigned char *scan_end; + unsigned char data[MAX_NETBIOS_REPLY_DATA_SIZE]; + net_cfg_t cfg; + + bzero(&packet, sizeof (struct name_packet)); + bzero(&answer, sizeof (struct resource_record)); + + packet.name_trn_id = original_packet->name_trn_id; + packet.info = NODE_STATUS_RESPONSE; + packet.qdcount = 0; /* question entries */ + packet.question = NULL; + packet.ancount = 1; /* answer recs */ + packet.answer = &answer; + packet.nscount = 0; /* authority recs */ + packet.authority = NULL; + packet.arcount = 0; /* additional recs */ + packet.additional = NULL; + + answer.name = original_packet->question->name; + answer.rr_type = NAME_RR_TYPE_NBSTAT; + answer.rr_class = NAME_QUESTION_CLASS_IN; + answer.ttl = 0; + answer.rdata = data; + + scan = smb_netbios_cache_status(data, MAX_NETBIOS_REPLY_DATA_SIZE, + original_packet->question->name->scope); + + scan_end = data + MAX_NETBIOS_REPLY_DATA_SIZE; + + if (smb_nic_get_bysubnet(addr->sin.sin_addr.s_addr, &cfg) == NULL) + net_ipaddr = 0; + else + net_ipaddr = cfg.ip; + + smb_config_rdlock(); + max_connections = smb_config_getnum(SMB_CI_MAX_CONNECTIONS); + smb_config_unlock(); + for (;;) { + if ((scan + 6) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + + if (net_ipaddr != 0) { + struct sockaddr_in *s_in; + int s; + + s = socket(AF_INET, SOCK_DGRAM, 0); + /* LINTED - E_BAD_PTR_CAST_ALIGN */ + s_in = (struct sockaddr_in *)&arpreq.arp_pa; + s_in->sin_family = AF_INET; + s_in->sin_addr.s_addr = net_ipaddr; + if (ioctl(s, SIOCGARP, (caddr_t)&arpreq) < 0) { + bzero(scan, 6); + } else { + bcopy(&arpreq.arp_ha.sa_data, scan, 6); + } + (void) close(s); + } else { + bzero(scan, 6); + } + scan += 6; + + if ((scan + 26) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + bzero(scan, 26); + scan += 26; + + if ((scan + 2) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + BE_OUT16(scan, 0); scan += 2; + + if ((scan + 2) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + BE_OUT16(scan, 0); scan += 2; + + if ((scan + 2) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + BE_OUT16(scan, 0); scan += 2; + + if ((scan + 2) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + BE_OUT16(scan, 0); scan += 2; + + if ((scan + 2) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + BE_OUT16(scan, 0); scan += 2; + + if ((scan + 2) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + BE_OUT16(scan, 0); scan += 2; + + if ((scan + 2) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + BE_OUT16(scan, 0); scan += 2; + + if ((scan + 2) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + BE_OUT16(scan, max_connections); scan += 2; + + if ((scan + 2) >= scan_end) { + packet.info |= NAME_NM_FLAGS_TC; + break; + } + + BE_OUT16(scan, 0); scan += 2; + + break; + /*NOTREACHED*/ + } + answer.rdlength = scan - data; + return (smb_send_name_service_packet(addr, &packet)); +} + +/* + * + * 5.1. NAME SERVICE PROTOCOLS + * + * A REQUEST packet is always sent to the well known UDP port - + * NAME_SERVICE_UDP_PORT. The destination address is normally + * either the IP broadcast address or the address of the NAME - the + * address of the NAME server it set up at initialization time. In + * rare cases, a request packet will be sent to an end node, e.g. a + * NAME QUERY REQUEST sent to "challenge" a node. + * + * A RESPONSE packet is always sent to the source UDP port and + * source IP address of the request packet. + * + * A DEMAND packet must always be sent to the well known UDP port - + * NAME_SERVICE_UDP_PORT. There is no restriction on the target IP + * address. + * + * Terms used in this section: + * + * tid - Transaction ID. This is a value composed from + * the requestor's IP address and a unique 16 bit + * value generated by the originator of the + * transaction. + */ + + +/* + * + * 5.1.1. B-NODE ACTIVITY + * + * 5.1.1.1. B-NODE ADD NAME + * + * PROCEDURE add_name(name) + * + * (* + * * Host initiated processing for a B node + * *) + * BEGIN + * + * REPEAT + * + * (* build name service packet *) + * + * ONT = B_NODE; (* broadcast node *) + * G = UNIQUE; (* unique name *) + * TTL = 0; + * + * broadcast NAME REGISTRATION REQUEST packet; + * + * (* + * * remote node(s) will send response packet + * * if applicable + * *) + * pause(BCAST_REQ_RETRY_TIMEOUT); + * + * UNTIL response packet is received or + * retransmit count has been exceeded + * + * IF no response packet was received THEN + * BEGIN (* no response *) + * (* + * * Build packet + * *) + * + * ONT = B_NODE; (* broadcast node *) + * G = UNIQUE; (* unique name *) + * TTL = 0; + * + * (* + * * Let other nodes known you have the name + * *) + * + * broadcast NAME UPDATE REQUEST packet; + * (* name can be added to local name table *) + * return success; + * END (* no response *) + * ELSE + * BEGIN (* got response *) + * + * (* + * * Match return transaction id + * * against tid sent in request + * *) + * + * IF NOT response tid = request tid THEN + * BEGIN + * ignore response packet; + * END + * ELSE + * CASE packet type OF + * + * NEGATIVE NAME REGISTRATION RESPONSE: + * + * return failure; (* name cannot be added *) + * + * POSITIVE NAME REGISTRATION RESPONSE: + * END-NODE CHALLENGE NAME REGISTRATION RESPONSE: + * + * (* + * * B nodes should normally not get this + * * response. + * *) + * + * ignore packet; + * END (* case *); + * END (* got response *) + * END (* procedure *) + * + * + * + * 5.1.1.2. B-NODE ADD_GROUP NAME + * + * PROCEDURE add_group_name(name) + * + * (* + * * Host initiated processing for a B node + * *) + * + * BEGIN + * (* + * * same as for a unique name with the + * * exception that the group bit (G) must + * * be set in the request packets. + * *) + * + * ... + * G = GROUP; + * ... + * ... + * + * (* + * * broadcast request ... + * *) + * + * + * END + */ +static int +smb_name_Bnode_add_name(struct name_entry *name) +{ + struct name_question question; + struct resource_record additional; + unsigned char data[8]; + unsigned short attr; + struct addr_entry *addr; + int rc = 0; + + addr = &name->addr_list; + + do { + /* build name service packet */ + question.name = name; + /* + * question.name->attributes |= NAME_NB_FLAGS_ONT_B; + * This is commented because NAME_NB_FLAGS_ONT_B is 0 + */ + question.question_type = NAME_QUESTION_TYPE_NB; + question.question_class = NAME_QUESTION_CLASS_IN; + + additional.name = name; + additional.rr_class = NAME_QUESTION_CLASS_IN; + additional.ttl = 0; + additional.rdata = data; + additional.rdlength = 6; + additional.rr_type = NAME_QUESTION_TYPE_NB; + attr = name->attributes & (NAME_ATTR_GROUP | + NAME_ATTR_OWNER_NODE_TYPE); + + BE_OUT16(&data[0], attr); + (void) memcpy(&data[2], &addr->sin.sin_addr.s_addr, + sizeof (uint32_t)); + + rc |= smb_send_name_registration_request(BROADCAST, &question, + &additional); + addr = addr->forw; + + } while (addr != &name->addr_list); + + return (rc); +} + +/* + * 5.1.1.3. B-NODE FIND_NAME + * + * PROCEDURE find_name(name) + * + * (* + * * Host initiated processing for a B node + * *) + * + * BEGIN + * + * REPEAT + * (* + * * build packet + * *) + * ONT = B; + * TTL = 0; + * G = DONT CARE; + * raddr = raddr->forw; + * + * broadcast NAME QUERY REQUEST packet; + * (* + * * a node might send response packet + * *) + * + * pause(BCAST_REQ_RETRY_TIMEOUT); + * UNTIL response packet received OR + * max transmit threshold exceeded + * + * IF no response packet received THEN + * return failure; + * ELSE + * IF NOT response tid = request tid THEN + * ignore packet; + * ELSE + * CASE packet type OF + * POSITIVE NAME QUERY RESPONSE: + * (* + * * Start a timer to detect conflict. + * * + * * Be prepared to detect conflict if + * * any more response packets are received. + * * + * *) + * + * save response as authoritative response; + * start_timer(CONFLICT_TIMER); + * return success; + * + * NEGATIVE NAME QUERY RESPONSE: + * REDIRECT NAME QUERY RESPONSE: + * + * (* + * * B Node should normally not get either + * * response. + * *) + * + * ignore response packet; + * + * END (* case *) + * END (* procedure *) + */ +static int +smb_name_Bnode_find_name(struct name_entry *name) +{ + struct name_question question; + + question.name = name; + question.question_type = NAME_QUESTION_TYPE_NB; + question.question_class = NAME_QUESTION_CLASS_IN; + + return (smb_send_name_query_request(BROADCAST, &question)); +} + +/* + * 5.1.1.4. B NODE NAME RELEASE + * + * PROCEDURE delete_name (name) + * BEGIN + * + * REPEAT + * + * (* + * * build packet + * *) + * ... + * + * (* + * * send request + * *) + * + * broadcast NAME RELEASE REQUEST packet; + * + * (* + * * no response packet expected + * *) + * + * pause(BCAST_REQ_RETRY_TIMEOUT); + * + * UNTIL retransmit count has been exceeded + * END (* procedure *) + */ +static int +smb_name_Bnode_delete_name(struct name_entry *name) +{ + struct name_question question; + struct resource_record additional; + struct addr_entry *raddr; + unsigned char data[MAX_DATAGRAM_LENGTH]; + unsigned char *scan = data; + uint32_t attr; + + /* build packet */ + question.name = name; + question.question_type = NAME_QUESTION_TYPE_NB; + question.question_class = NAME_QUESTION_CLASS_IN; + + additional.name = name; + additional.rr_class = NAME_QUESTION_CLASS_IN; + additional.ttl = 0; + additional.rdata = data; + additional.rdlength = 0; + additional.rr_type = NAME_QUESTION_TYPE_NB; + raddr = &name->addr_list; + scan = data; + do { + attr = name->attributes & (NAME_ATTR_GROUP | + NAME_ATTR_OWNER_NODE_TYPE); + + BE_OUT16(scan, attr); scan += 2; + (void) memcpy(scan, &raddr->sin.sin_addr.s_addr, + sizeof (uint32_t)); + scan += 4; + + additional.rdlength += 6; + } while (raddr != &name->addr_list); + + return (smb_send_name_release_request_and_demand(BROADCAST, + &question, &additional)); +} + +/* + * + * 5.1.2. P-NODE ACTIVITY + * + * All packets sent or received by P nodes are unicast UDP packets. + * A P node sends name service requests to the NAME node that is + * specified in the P-node configuration. + * + * 5.1.2.1. P-NODE ADD_NAME + * + * PROCEDURE add_name(name) + * + * (* + * * Host initiated processing for a P node + * *) + * + * BEGIN + * + * REPEAT + * (* + * * build packet + * *) + * + * ONT = P; + * G = UNIQUE; + * ... + * + * (* + * * send request + * *) + * + * unicast NAME REGISTRATION REQUEST packet; + * + * (* + * * NAME will send response packet + * *) + * + * IF receive a WACK RESPONSE THEN + * pause(time from TTL field of response); + * ELSE + * pause(UCAST_REQ_RETRY_TIMEOUT); + * UNTIL response packet is received OR + * retransmit count has been exceeded + * + * IF no response packet was received THEN + * BEGIN (* no response *) + * (* + * * NAME is down. Cannot claim name. + * *) + * + * return failure; (* name cannot be claimed *) + * END (* no response *) + * ELSE + * BEGIN (* response *) + * IF NOT response tid = request tid THEN + * BEGIN + * (* Packet may belong to another transaction *) + * ignore response packet; + * END + * ELSE + * CASE packet type OF + * + * POSITIVE NAME REGISTRATION RESPONSE: + * + * (* + * * name can be added + * *) + * + * adjust refresh timeout value, TTL, for this name; + * return success; (* name can be added *) + * + * NEGATIVE NAME REGISTRATION RESPONSE: + * return failure; (* name cannot be added *) + * + * END-NODE CHALLENGE REGISTRATION REQUEST: + * BEGIN (* end node challenge *) + * + * (* + * * The response packet has in it the + * * address of the presumed owner of the + * * name. Challenge that owner. + * * If owner either does not + * * respond or indicates that he no longer + * * owns the name, claim the name. + * * Otherwise, the name cannot be claimed. + * * + * *) + * + * REPEAT + * (* + * * build packet + * *) + * ... + * + * unicast NAME QUERY REQUEST packet to the + * address contained in the END NODE + * CHALLENGE RESPONSE packet; + * + * (* + * * remote node may send response packet + * *) + * + * pause(UCAST_REQ_RETRY_TIMEOUT); + * + * UNTIL response packet is received or + * retransmit count has been exceeded + * IF no response packet is received OR + * NEGATIVE NAME QUERY RESPONSE packet + * received THEN + * BEGIN (* update *) + * + * (* + * * name can be claimed + * *) + * + * REPEAT + * + * (* + * * build packet + * *) + * ... + * + * unicast NAME UPDATE REQUEST to NAME; + * + * (* + * * NAME node will send response packet + * *) + * + * IF receive a WACK RESPONSE THEN + * pause(time from TTL field of response); + * ELSE + * pause(UCAST_REQ_RETRY_TIMEOUT); + * UNTIL response packet is received or + * retransmit count has been exceeded + * IF no response packet received THEN + * BEGIN (* no response *) + * + * (* + * * name could not be claimed + * *) + * + * return failure; + * END (* no response *) + * ELSE + * CASE packet type OF + * POSITIVE NAME REGISTRATION RESPONSE: + * (* + * * add name + * *) + * return success; + * NEGATIVE NAME REGISTRATION RESPONSE: + * + * (* + * * you lose ... + * *) + * return failure; + * END (* case *) + * END (* update *) + * ELSE + * + * (* + * * received a positive response to the "challenge" + * * Remote node still has name + * *) + * + * return failure; + * END (* end node challenge *) + * END (* response *) + * END (* procedure *) + * + * + * 5.1.2.2. P-NODE ADD GROUP NAME + * + * PROCEDURE add_group_name(name) + * + * (* + * * Host initiated processing for a P node + * *) + * + * BEGIN + * (* + * * same as for a unique name, except that the + * * request packet must indicate that a + * * group name claim is being made. + * *) + * + * ... + * G = GROUP; + * ... + * + * (* + * * send packet + * *) + * ... + * + * + * END + */ +static int +smb_name_Pnode_add_name(struct name_entry *name) +{ + struct name_question question; + struct resource_record additional; + unsigned char data[8]; + unsigned short attr; + struct addr_entry *addr; + int rc = 0; + + /* build packet */ + addr = &name->addr_list; + do { + question.name = name; + question.question_type = NAME_QUESTION_TYPE_NB; + question.question_class = NAME_QUESTION_CLASS_IN; + + additional.name = name; + additional.rr_class = NAME_QUESTION_CLASS_IN; + additional.ttl = 0; + additional.rdata = data; + additional.rdlength = 6; + additional.rr_type = NAME_QUESTION_TYPE_NB; + attr = name->attributes & + (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE); + + BE_OUT16(&data[0], attr); + (void) memcpy(&data[2], &addr->sin.sin_addr.s_addr, + sizeof (uint32_t)); + + rc |= smb_send_name_registration_request(UNICAST, &question, + &additional); + + addr = addr->forw; + + } while (addr != &name->addr_list); + + return (rc); +} + +static int +smb_name_Pnode_refresh_name(struct name_entry *name) +{ + struct name_question question; + struct resource_record additional; + unsigned char data[8]; + unsigned short attr; + struct addr_entry *addr; + int rc = 0; + + /* build packet */ + addr = &name->addr_list; + do { + question.name = name; + question.question_type = NAME_QUESTION_TYPE_NB; + question.question_class = NAME_QUESTION_CLASS_IN; + + additional.name = name; + additional.rr_class = NAME_QUESTION_CLASS_IN; + additional.ttl = 0; + additional.rdata = data; + additional.rdlength = 6; + additional.rr_type = NAME_QUESTION_TYPE_NB; + attr = name->attributes & + (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE); + + BE_OUT16(&data[0], attr); + (void) memcpy(&data[2], &addr->sin.sin_addr.s_addr, + sizeof (uint32_t)); + + rc |= smb_send_name_refresh_request(UNICAST, &question, + &additional, 1); + + addr = addr->forw; + } while (addr != &name->addr_list); + + return (rc); +} + +/* + * 5.1.2.3. P-NODE FIND NAME + * + * PROCEDURE find_name(name) + * + * (* + * * Host initiated processing for a P node + * *) + * + * BEGIN + * REPEAT + * (* + * * build packet + * *) + * + * ONT = P; + * G = DONT CARE; + * + * unicast NAME QUERY REQUEST packet; + * + * (* + * * a NAME node might send response packet + * *) + * + * IF receive a WACK RESPONSE THEN + * pause(time from TTL field of response); + * ELSE + * pause(UCAST_REQ_RETRY_TIMEOUT); + * UNTIL response packet received OR + * max transmit threshold exceeded + * + * IF no response packet received THEN + * return failure; + * ELSE + * IF NOT response tid = request tid THEN + * ignore packet; + * ELSE + * CASE packet type OF + * POSITIVE NAME QUERY RESPONSE: + * return success; + * + * REDIRECT NAME QUERY RESPONSE: + * + * (* + * * NAME node wants this end node + * * to use some other NAME node + * * to resolve the query. + * *) + * + * repeat query with NAME address + * in the response packet; + * NEGATIVE NAME QUERY RESPONSE: + * return failure; + * + * END (* case *) + * END (* procedure *) + */ +static int +smb_name_Pnode_find_name(struct name_entry *name) +{ + struct name_question question; + + /* + * Host initiated processing for a P node + */ + question.name = name; + question.name->attributes |= NAME_NB_FLAGS_ONT_P; + question.question_type = NAME_QUESTION_TYPE_NB; + question.question_class = NAME_QUESTION_CLASS_IN; + + return (smb_send_name_query_request(UNICAST, &question)); +} + +/* + * 5.1.2.4. P-NODE DELETE_NAME + * + * PROCEDURE delete_name (name) + * + * (* + * * Host initiated processing for a P node + * *) + * + * BEGIN + * + * REPEAT + * + * (* + * * build packet + * *) + * ... + * + * (* + * * send request + * *) + * + * unicast NAME RELEASE REQUEST packet; + * IF receive a WACK RESPONSE THEN + * pause(time from TTL field of response); + * ELSE + * pause(UCAST_REQ_RETRY_TIMEOUT); + * UNTIL retransmit count has been exceeded + * or response been received + * + * IF response has been received THEN + * CASE packet type OF + * POSITIVE NAME RELEASE RESPONSE: + * return success; + * NEGATIVE NAME RELEASE RESPONSE: + * + * (* + * * NAME does want node to delete this + * * name !!! + * *) + * + * return failure; + * END (* case *) + * END (* procedure *) + */ +static int +smb_name_Pnode_delete_name(struct name_entry *name) +{ + struct name_question question; + struct resource_record additional; + struct addr_entry *raddr; + unsigned char data[MAX_DATAGRAM_LENGTH]; + unsigned char *scan = data; + uint32_t attr; + + /* build packet */ + question.name = name; + question.name->attributes |= NAME_NB_FLAGS_ONT_P; + question.question_type = NAME_QUESTION_TYPE_NB; + question.question_class = NAME_QUESTION_CLASS_IN; + + additional.name = name; + additional.rr_class = NAME_QUESTION_CLASS_IN; + additional.ttl = 0; + additional.rdata = data; + additional.rdlength = 0; + additional.rr_type = NAME_QUESTION_TYPE_NB; + raddr = &name->addr_list; + do { + scan = data; + attr = name->attributes & (NAME_ATTR_GROUP | + NAME_ATTR_OWNER_NODE_TYPE); + + BE_OUT16(scan, attr); scan += 2; + BE_OUT32(scan, raddr->sin.sin_addr.s_addr); scan += 4; + (void) memcpy(scan, &raddr->sin.sin_addr.s_addr, + sizeof (uint32_t)); + scan += 4; + + additional.rdlength = 6; + raddr = raddr->forw; + (void) smb_send_name_release_request_and_demand(UNICAST, + &question, &additional); + } while (raddr != &name->addr_list); + + return (1); +} + +/* + * 5.1.3. M-NODE ACTIVITY + * + * M nodes behavior is similar to that of P nodes with the addition + * of some B node-like broadcast actions. M node name service + * proceeds in two steps: + * + * 1.Use broadcast UDP based name service. Depending on the + * operation, goto step 2. + * + * 2.Use directed UDP name service. + * + * The following code for M nodes is exactly the same as for a P + * node, with the exception that broadcast operations are done + * before P type operation is attempted. + * + * 5.1.3.1. M-NODE ADD NAME + * + * PROCEDURE add_name(name) + * + * (* + * * Host initiated processing for a M node + * *) + * + * BEGIN + * + * (* + * * check if name exists on the + * * broadcast area + * *) + * REPEAT + * (* build packet *) + * + * .... + * broadcast NAME REGISTRATION REQUEST packet; + * pause(BCAST_REQ_RETRY_TIMEOUT); + * + * UNTIL response packet is received or + * retransmit count has been exceeded + * + * IF valid response received THEN + * BEGIN + * (* cannot claim name *) + * + * return failure; + * END + * + * (* + * * No objections received within the + * * broadcast area. + * * Send request to name server. + * *) + * + * REPEAT + * (* + * * build packet + * *) + * + * ONT = M; + * ... + * + * unicast NAME REGISTRATION REQUEST packet; + * + * (* + * * remote NAME will send response packet + * *) + * + * IF receive a WACK RESPONSE THEN + * pause(time from TTL field of response); + * ELSE + * pause(UCAST_REQ_RETRY_TIMEOUT); + * + * UNTIL response packet is received or + * retransmit count has been exceeded + * + * IF no response packet was received THEN + * BEGIN (* no response *) + * (* + * * NAME is down. Cannot claim name. + * *) + * return failure; (* name cannot be claimed *) + * END (* no response *) + * ELSE + * BEGIN (* response *) + * IF NOT response tid = request tid THEN + * BEGIN + * ignore response packet; + * END + * ELSE + * CASE packet type OF + * POSITIVE NAME REGISTRATION RESPONSE: + * + * (* + * * name can be added + * *) + * + * adjust refresh timeout value, TTL; + * return success; (* name can be added *) + * + * NEGATIVE NAME REGISTRATION RESPONSE: + * return failure; (* name cannot be added *) + * + * END-NODE CHALLENGE REGISTRATION REQUEST: + * BEGIN (* end node challenge *) + * + * (* + * * The response packet has in it the + * * address of the presumed owner of the + * * name. Challenge that owner. + * * If owner either does not + * * respond or indicates that he no longer + * * owns the name, claim the name. + * * Otherwise, the name cannot be claimed. + * * + * *) + * + * REPEAT + * (* + * * build packet + * *) + * ... + * + * (* + * * send packet to address contained in the + * * response packet + * *) + * + * unicast NAME QUERY REQUEST packet; + * + * (* + * * remote node may send response packet + * *) + * + * pause(UCAST_REQ_RETRY_TIMEOUT); + * + * UNTIL response packet is received or + * retransmit count has been exceeded + * IF no response packet is received THEN + * BEGIN (* no response *) + * + * (* + * * name can be claimed + * *) + * REPEAT + * + * (* + * * build packet + * *) + * ... + * + * unicast NAME UPDATE REQUEST to NAME; + * + * (* + * * NAME node will send response packet + * *) + * + * IF receive a WACK RESPONSE THEN + * pause(time from TTL field of response); + * ELSE + * pause(UCAST_REQ_RETRY_TIMEOUT); + * + * UNTIL response packet is received or + * retransmit count has been exceeded + * IF no response packet received THEN + * BEGIN (* no response *) + * + * (* + * * name could not be claimed + * *) + * + * return failure; + * END (* no response *) + * ELSE + * CASE packet type OF + * POSITIVE NAME REGISTRATION RESPONSE: + * (* + * * add name + * *) + * + * return success; + * NEGATIVE NAME REGISTRATION RESPONSE: + * (* + * * you lose ... + * *) + * + * return failure; + * END (* case *) + * END (* no response *) + * ELSE + * IF NOT response tid = request tid THEN + * BEGIN + * ignore response packet; + * END + * + * (* + * * received a response to the "challenge" + * * packet + * *) + * + * CASE packet type OF + * POSITIVE NAME QUERY: + * + * (* + * * remote node still has name. + * *) + * + * return failure; + * NEGATIVE NAME QUERY: + * + * (* + * * remote node no longer has name + * *) + * + * return success; + * END (* case *) + * END (* end node challenge *) + * END (* case *) + * END (* response *) + * END (* procedure *) + * + * + * 5.1.3.2. M-NODE ADD GROUP NAME + * + * PROCEDURE add_group_name(name) + * + * (* + * * Host initiated processing for a P node + * *) + * + * BEGIN + * (* + * * same as for a unique name, except that the + * * request packet must indicate that a + * * group name claim is being made. + * *) + * + * ... + * G = GROUP; + * ... + * + * (* + * * send packet + * *) + * ... + * + * + * END + */ +static int +smb_name_Mnode_add_name(struct name_entry *name) +{ + if (smb_name_Bnode_add_name(name) > 0) { + if (nbns_num == 0) + return (1); /* No name server configured */ + + return (smb_name_Pnode_add_name(name)); + } + return (-1); +} + +static int +smb_name_Hnode_add_name(struct name_entry *name) +{ + if (nbns_num > 0) { + if (smb_name_Pnode_add_name(name) == 1) + return (1); + } + + return (smb_name_Bnode_add_name(name)); +} + +/* + * 5.1.3.3. M-NODE FIND NAME + * + * PROCEDURE find_name(name) + * + * (* + * * Host initiated processing for a M node + * *) + * + * BEGIN + * (* + * * check if any node on the broadcast + * * area has the name + * *) + * + * REPEAT + * (* build packet *) + * ... + * + * broadcast NAME QUERY REQUEST packet; + * pause(BCAST_REQ_RETRY_TIMEOUT); + * UNTIL response packet received OR + * max transmit threshold exceeded + * + * IF valid response received THEN + * BEGIN + * save response as authoritative response; + * start_timer(CONFLICT_TIMER); + * return success; + * END + * + * (* + * * no valid response on the b'cast segment. + * * Try the name server. + * *) + * + * REPEAT + * (* + * * build packet + * *) + * + * ONT = M; + * G = DONT CARE; + * + * unicast NAME QUERY REQUEST packet to NAME; + * + * (* + * * a NAME node might send response packet + * *) + * + * IF receive a WACK RESPONSE THEN + * pause(time from TTL field of response); + * ELSE + * pause(UCAST_REQ_RETRY_TIMEOUT); + * UNTIL response packet received OR + * max transmit threshold exceeded + * + * IF no response packet received THEN + * return failure; + * ELSE + * IF NOT response tid = request tid THEN + * ignore packet; + * ELSE + * CASE packet type OF + * POSITIVE NAME QUERY RESPONSE: + * return success; + * + * REDIRECT NAME QUERY RESPONSE: + * + * (* + * * NAME node wants this end node + * * to use some other NAME node + * * to resolve the query. + * *) + * + * repeat query with NAME address + * in the response packet; + * NEGATIVE NAME QUERY RESPONSE: + * return failure; + * + * END (* case *) + * END (* procedure *) + */ +static int +smb_name_Mnode_find_name(struct name_entry *name) +{ + if (smb_name_Bnode_find_name(name) == 1) + return (1); + + if (nbns_num == 0) + return (1); /* No name server configured */ + + return (smb_name_Pnode_find_name(name)); +} + +static int +smb_name_Hnode_find_name(struct name_entry *name) +{ + if (nbns_num > 0) + if (smb_name_Pnode_find_name(name) == 1) + return (1); + + return (smb_name_Bnode_find_name(name)); +} + +/* + * 5.1.3.4. M-NODE DELETE NAME + * + * PROCEDURE delete_name (name) + * + * (* + * * Host initiated processing for a P node + * *) + * + * BEGIN + * (* + * * First, delete name on NAME + * *) + * + * REPEAT + * + * (* + * * build packet + * struct addr_entry *addr; + * *) + * ... + * + * (* + * * send request + * *) + * + * unicast NAME RELEASE REQUEST packet to NAME; + * + * IF receive a WACK RESPONSE THEN + * pause(time from TTL field of response); + * ELSE + * pause(UCAST_REQ_RETRY_TIMEOUT); + * UNTIL retransmit count has been exceeded + * or response been received + * + * IF response has been received THEN + * CASE packet type OF + * POSITIVE NAME RELEASE RESPONSE: + * (* + * * Deletion of name on b'cast segment is deferred + * * until after NAME has deleted the name + * *) + * + * REPEAT + * (* build packet *) + * + * ... + * broadcast NAME RELEASE REQUEST; + * pause(BCAST_REQ_RETRY_TIMEOUT); + * UNTIL rexmt threshold exceeded + * + * return success; + * NEGATIVE NAME RELEASE RESPONSE: + * + * (* + * * NAME does want node to delete this + * * name + * *) + * return failure; + * END (* case *) + * END (* procedure *) + */ +static int +smb_name_Mnode_delete_name(struct name_entry *name) +{ + (void) smb_name_Bnode_delete_name(name); + + if (nbns_num == 0) + return (-1); /* No name server configured */ + + if (smb_name_Pnode_delete_name(name) > 0) + return (1); + + return (-1); +} + +static int +smb_name_Hnode_delete_name(struct name_entry *name) +{ + if (nbns_num > 0) + if (smb_name_Pnode_delete_name(name) > 0) + return (1); + + return (smb_name_Bnode_delete_name(name)); +} + +/* + * 5.1.1.5. B-NODE INCOMING PACKET PROCESSING + * + * Following processing is done when broadcast or unicast packets + * are received at the NAME_SERVICE_UDP_PORT. + * + * PROCEDURE process_incoming_packet(packet) + * + * (* + * * Processing initiated by incoming packets for a B node + * *) + * + * BEGIN + * (* + * * Note: response packets are always sent + * * to: + * * source IP address of request packet + * * source UDP port of request packet + * *) + * + * CASE packet type OF + * + * NAME REGISTRATION REQUEST (UNIQUE): + * IF name exists in local name table THEN + * send NEGATIVE_NAME_REGISTRATION_RESPONSE ; + * NAME REGISTRATION REQUEST (GROUP): + * IF name exists in local name table THEN + * BEGIN + * IF local entry is a unique name THEN + * send NEGATIVE_NAME_REGISTRATION_RESPONSE ; + * END + * NAME QUERY REQUEST: + * IF name exists in local name table THEN + * BEGIN + * build response packet; + * send POSITIVE_NAME_QUERY_RESPONSE; + * POSITIVE NAME QUERY RESPONSE: + * IF name conflict timer is not active THEN + * BEGIN + * (* + * * timer has expired already... ignore this + * * packet + * *) + * + * return; + * END + * ELSE (* timer is active *) + * IF a response for this name has previously been + * received THEN + * BEGIN (* existing entry *) + * + * (* + * * we sent out a request packet, and + * * have already received (at least) + * * one response + * * + * * Check if conflict exists. + * * If so, send out a conflict packet. + * * + * * Note: detecting conflict does NOT + * * affect any existing sessions. + * * + * *) + * + * (* + * * Check for name conflict. + * * See "Name Conflict" in Concepts and Methods + * *) + * check saved authoritative response against + * information in this response packet; + * IF conflict detected THEN + * BEGIN + * unicast NAME CONFLICT DEMAND packet; + * IF entry exists in cache THEN + * BEGIN + * remove entry from cache; + * END + * END + * END (* existing entry *) + * ELSE + * BEGIN + * (* + * * Note: If this was the first response + * * to a name query, it would have been + * * handled in the + * * find_name() procedure. + * *) + * + * ignore packet; + * END + * NAME CONFLICT DEMAND: + * IF name exists in local name table THEN + * BEGIN + * mark name as conflict detected; + * + * (* + * * a name in the state "conflict detected" + * * does not "logically" exist on that node. + * * No further session will be accepted on + * * that name. + * * No datagrams can be sent against that name. + * * Such an entry will not be used for + * * purposes of processing incoming request + * * packets. + * * The only valid user NetBIOS operation + * * against such a name is DELETE NAME. + * *) + * END + * NAME RELEASE REQUEST: + * IF caching is being done THEN + * BEGIN + * remove entry from cache; + * END + * NAME UPDATE REQUEST: + * IF caching is being done THEN + * BEGIN + * IF entry exists in cache already, + * update cache; + * ELSE IF name is "interesting" THEN + * BEGIN + * add entry to cache; + * END + * END + * + * NODE STATUS REQUEST: + * IF name exists in local name table THEN + * BEGIN + * (* + * * send only those names that are + * * in the same scope as the scope + * * field in the request packet + * *) + * + * send NODE STATUS RESPONSE; + * END + * END + */ +static void +smb_name_process_Bnode_packet(struct name_packet *packet, + struct addr_entry *addr) +{ + struct name_entry *name; + struct name_entry *entry; + struct name_question *question; + struct resource_record *additional; + + question = packet->question; + additional = packet->additional; + + switch (packet->info & NAME_OPCODE_OPCODE_MASK) { + case NAME_OPCODE_REFRESH: + /* Guard against malformed packets */ + if ((question == 0) || (additional == 0)) + break; + if (additional->name->addr_list.sin.sin_addr.s_addr == 0) + break; + + name = question->name; + name->addr_list.ttl = additional->ttl; + name->attributes = additional->name->attributes; + name->addr_list.sin = additional->name->addr_list.sin; + name->addr_list.forw = name->addr_list.back = &name->addr_list; + + if ((entry = smb_netbios_cache_lookup_addr(name)) != 0) { + smb_netbios_cache_update_entry(entry, question->name); + smb_netbios_cache_unlock_entry(entry); + } + else + (void) smb_netbios_cache_insert(question->name); + break; + + case NAME_OPCODE_QUERY: + /* + * This opcode covers both NAME_QUERY_REQUEST and + * NODE_STATUS_REQUEST. They can be distinguished + * based on the type of question entry. + */ + + /* All query requests have to have question entry */ + if (question == 0) + break; + + if (question->question_type == NAME_QUESTION_TYPE_NB) { + name = question->name; + if ((entry = smb_netbios_cache_lookup(name)) != 0) { + (void) smb_send_name_query_response(addr, + packet, entry, 0); + smb_netbios_cache_unlock_entry(entry); + } + } + else + if (question->question_type == NAME_QUESTION_TYPE_NBSTAT) { + /* + * Name of "*" may be used to force node to + * divulge status for administrative purposes + */ + name = question->name; + entry = 0; + if (NETBIOS_NAME_IS_STAR(name->name) || + ((entry = smb_netbios_cache_lookup(name)) != 0)) { + if (entry) + smb_netbios_cache_unlock_entry(entry); + /* + * send only those names that are + * in the same scope as the scope + * field in the request packet + */ + (void) smb_send_node_status_response(addr, + packet); + } + } + break; + + default: + break; + } +} + +/* + * 5.1.2.5. P-NODE INCOMING PACKET PROCESSING + * + * Processing initiated by reception of packets at a P node + * + * PROCEDURE process_incoming_packet(packet) + * + * (* + * * Processing initiated by incoming packets at a P node + * *) + * + * BEGIN + * (* + * * always ignore UDP broadcast packets + * *) + * + * IF packet was sent as a broadcast THEN + * BEGIN + * ignore packet; + * return; + * END + * CASE packet type of + * + * NAME CONFLICT DEMAND: + * IF name exists in local name table THEN + * mark name as in conflict; + * return; + * + * NAME QUERY REQUEST: + * IF name exists in local name table THEN + * BEGIN (* name exists *) + * + * (* + * * build packet + * *) + * ... + * + * (* + * * send response to the IP address and port + * * number from which the request was received. + * *) + * + * send POSITIVE_NAME_QUERY_RESPONSE ; + * return; + * END (* exists *) + * ELSE + * BEGIN (* does not exist *) + * + * (* + * * send response to the requestor + * *) + * + * send NEGATIVE_NAME_QUERY_RESPONSE ; + * return; + * END (* does not exist *) + * NODE STATUS REQUEST: + * (* + * * Name of "*" may be used for force node to + * * divulge status for administrative purposes + * *) + * IF name in local name table OR name = "*" THEN + * BEGIN + * (* + * * Build response packet and + * * send to requestor node + * * Send only those names that are + * * in the same scope as the scope + * * in the request packet. + * *) + * + * send NODE_STATUS_RESPONSE; + * END + * + * NAME RELEASE REQUEST: + * (* + * * This will be received if the NAME wants to flush the + * * name from the local name table, or from the local + * * cache. + * *) + * + * IF name exists in the local name table THEN + * BEGIN + * delete name from local name table; + * inform user that name has been deleted; + * END + * END (* case *) + * END (* procedure *) + * + * (* + * * Incoming packet processing on a NS node + * *) + * + * BEGIN + * IF packet was sent as a broadcast THEN + * BEGIN + * discard packet; + * return; + * END + * CASE packet type of + * + * NAME REGISTRATION REQUEST (UNIQUE): + * IF unique name exists in data base THEN + * BEGIN (* unique name exists *) + * (* + * * NAME node may be a "passive" + * * server in that it expects the + * * end node to do the challenge + * * server. Such a NAME node is + * * called a "non-secure" server. + * * A "secure" server will do the + * * challenging before it sends + * * back a response packet. + * *) + * + * IF non-secure THEN + * BEGIN + * (* + * * build response packet + * *) + * ... + * + * + * (* + * * let end node do the challenge + * *) + * + * send END-NODE CHALLENGE NAME REGISTRATION + * RESPONSE; + * return; + * END + * ELSE + * (* + * * secure server - do the name + * * challenge operation + * *) + * + * REPEAT + * send NAME QUERY REQUEST; + * pause(UCAST_REQ_RETRY_TIMEOUT); + * UNTIL response has been received or + * retransmit count has been exceeded + * IF no response was received THEN + * BEGIN + * + * (* node down *) + * + * update data base - remove entry; + * update data base - add new entry; + * send POSITIVE NAME REGISTRATION RESPONSE; + * return; + * END + * ELSE + * BEGIN (* challenged node replied *) + * (* + * * challenged node replied with + * * a response packet + * *) + * + * CASE packet type + * + * POSITIVE NAME QUERY RESPONSE: + * + * (* + * * name still owned by the + * * challenged node + * * + * * build packet and send response + * *) + * ... + * + * + * (* + * * Note: The NAME will need to + * * keep track (based on transaction id) of + * * the IP address and port number + * * of the original requestor. + * *) + * + * send NEGATIVE NAME REGISTRATION RESPONSE; + * return; + * NEGATIVE NAME QUERY RESPONSE: + * + * update data base - remove entry; + * update data base - add new entry; + * + * (* + * * build response packet and send + * * response + * *) + * send POSITIVE NAME REGISTRATION RESPONSE; + * return; + * END (* case *) + * END (* challenged node replied *) + * END (* unique name exists in data base *) + * ELSE + * IF group name exists in data base THEN + * BEGIN (* group names exists *) + * + * (* + * * Members of a group name are NOT + * * challenged. + * * Make the assumption that + * * at least some of the group members + * * are still alive. + * * Refresh mechanism will + * * allow the NAME to detect when all + * * members of a group no longer use that + * * name + * *) + * + * send NEGATIVE NAME REGISTRATION RESPONSE; + * END (* group name exists *) + * ELSE + * BEGIN (* name does not exist *) + * + * (* + * * Name does not exist in data base + * * + * * This code applies to both non-secure + * * and secure server. + * *) + * + * update data base - add new entry; + * send POSITIVE NAME REGISTRATION RESPONSE; + * return; + * END + * + * NAME QUERY REQUEST: + * IF name exists in data base THEN + * BEGIN + * (* + * * build response packet and send to + * * requestor + * *) + * ... + * + * send POSITIVE NAME QUERY RESPONSE; + * return; + * ELSE + * BEGIN + * (* + * * build response packet and send to + * * requestor + * *) + * ... + * + * send NEGATIVE NAME QUERY RESPONSE; + * return; + * END + * + * NAME REGISTRATION REQUEST (GROUP): + * IF name exists in data base THEN + * BEGIN + * IF local entry is a unique name THEN + * BEGIN (* local is unique *) + * + * IF non-secure THEN + * BEGIN + * send END-NODE CHALLENGE NAME + * REGISTRATION RESPONSE; + * return; + * END + * + * REPEAT + * send NAME QUERY REQUEST; + * pause(UCAST_REQ_RETRY_TIMEOUT); + * UNTIL response received or + * retransmit count exceeded + * IF no response received or + * NEGATIVE NAME QUERY RESPONSE + * received THEN + * BEGIN + * update data base - remove entry; + * update data base - add new entry; + * send POSITIVE NAME REGISTRATION RESPONSE; + * return; + * END + * ELSE + * BEGIN + * (* + * * name still being held + * * by challenged node + * *) + * + * send NEGATIVE NAME REGISTRATION RESPONSE; + * END + * END (* local is unique *) + * ELSE + * BEGIN (* local is group *) + * (* + * * existing entry is a group name + * *) + * + * update data base - remove entry; + * update data base - add new entry; + * send POSITIVE NAME REGISTRATION RESPONSE; + * return; + * END (* local is group *) + * END (* names exists *) + * ELSE + * BEGIN (* does not exist *) + * + * (* name does not exist in data base *) + * + * update data base - add new entry; + * send POSITIVE NAME REGISTRATION RESPONSE; + * return; + * END (* does not exist *) + * + * NAME RELEASE REQUEST: + * + * (* + * * secure server may choose to disallow + * * a node from deleting a name + * *) + * + * update data base - remove entry; + * send POSITIVE NAME RELEASE RESPONSE; + * return; + * + * NAME UPDATE REQUEST: + * + * (* + * * End-node completed a successful challenge, + * * no update database + * *) + * + * IF secure server THEN + * send NEGATIVE NAME REGISTRATION RESPONSE; + * ELSE + * BEGIN (* new entry *) + * IF entry already exists THEN + * update data base - remove entry; + * update data base - add new entry; + * send POSITIVE NAME REGISTRATION RESPONSE; + * start_timer(TTL); + * END + * + * NAME REFRESH REQUEST: + * check for consistency; + * + * IF node not allowed to have name THEN + * BEGIN + * + * (* + * * tell end node that it can't have name + * *) + * send NEGATIVE NAME REGISTRATION RESPONSE; + * END + * ELSE + * BEGIN + * + * (* + * * send confirmation response to the + * * end node. + * *) + * send POSITIVE NAME REGISTRATION; + * start_timer(TTL); + * END + * return; + * END (* case *) + * END (* procedure *) + */ +static void +smb_name_process_Pnode_packet(struct name_packet *packet, + struct addr_entry *addr) +{ + struct name_entry *name; + struct name_entry *entry; + struct name_question *question; + struct resource_record *additional; + + question = packet->question; + additional = packet->additional; + + if (packet->info & NAME_NM_FLAGS_B) { + /* + * always ignore UDP broadcast packets + */ + return; + } + + switch (packet->info & NAME_OPCODE_OPCODE_MASK) { + case NAME_OPCODE_REFRESH: + /* Guard against malformed packets */ + if ((question == 0) || (additional == 0)) + break; + if (additional->name->addr_list.sin.sin_addr.s_addr == 0) + break; + + name = question->name; + name->addr_list.ttl = additional->ttl; + name->attributes = additional->name->attributes; + name->addr_list.sin = additional->name->addr_list.sin; + name->addr_list.forw = name->addr_list.back = &name->addr_list; + + if ((entry = smb_netbios_cache_lookup(name)) != 0) { + smb_netbios_cache_update_entry(entry, name); + smb_netbios_cache_unlock_entry(entry); + } + else + (void) smb_netbios_cache_insert(name); + + (void) smb_send_name_registration_response(addr, packet, 0); + break; + + case NAME_OPCODE_QUERY: + /* + * This opcode covers both NAME_QUERY_REQUEST and + * NODE_STATUS_REQUEST. They can be distinguished + * based on the type of question entry. + */ + + /* All query requests have to have question entry */ + if (question == 0) + break; + + if (question->question_type == NAME_QUESTION_TYPE_NB) { + name = question->name; + if ((entry = smb_netbios_cache_lookup(name)) != 0) { + /* + * send response to the IP address and port + * number from which the request was received. + */ + (void) smb_send_name_query_response(addr, + packet, entry, 0); + smb_netbios_cache_unlock_entry(entry); + } else { + /* + * send response to the requestor + */ + (void) smb_send_name_query_response(addr, + packet, name, RCODE_NAM_ERR); + } + } + else + if (question->question_type == NAME_QUESTION_TYPE_NBSTAT) { + /* + * Name of "*" may be used to force node to + * divulge status for administrative purposes + */ + name = question->name; + entry = 0; + if (NETBIOS_NAME_IS_STAR(name->name) || + ((entry = smb_netbios_cache_lookup(name)) != 0)) { + /* + * send only those names that are + * in the same scope as the scope + * field in the request packet + */ + if (entry) + smb_netbios_cache_unlock_entry(entry); + (void) smb_send_node_status_response(addr, + packet); + } + } + break; + + default: + break; + } +} + +/* + * 5.1.3.5. M-NODE INCOMING PACKET PROCESSING + * + * Processing initiated by reception of packets at a M node + * + * PROCEDURE process_incoming_packet(packet) + * + * (* + * * Processing initiated by incoming packets at a M node + * *) + * + * BEGIN + * CASE packet type of + * + * NAME CONFLICT DEMAND: + * IF name exists in local name table THEN + * mark name as in conflict; + * return; + * + * NAME QUERY REQUEST: + * IF name exists in local name table THEN + * BEGIN (* name exists *) + * + * (* + * * build packet + * *) + * ... + * + * (* + * * send response to the IP address and port + * * number from which the request was received. + * *) + * + * send POSITIVE NAME QUERY RESPONSE ; + * return; + * END (* exists *) + * ELSE + * BEGIN (* does not exist *) + * + * (* + * * send response to the requestor + * *) + * + * IF request NOT broadcast THEN + * (* + * * Don't send negative responses to + * * queries sent by B nodes + * *) + * send NEGATIVE NAME QUERY RESPONSE ; + * return; + * END (* does not exist *) + * NODE STATUS REQUEST: + * BEGIN + * (* + * * Name of "*" may be used to force node to + * * divulge status for administrative purposes + * *) + * IF name in local name table OR name = "*" THEN + * (* + * * Build response packet and + * * send to requestor node + * * Send only those names that are + * * in the same scope as the scope + * * in the request packet. + * *) + * + * send NODE STATUS RESPONSE; + * END + * + * NAME RELEASE REQUEST: + * (* + * * This will be received if the NAME wants to flush the + * * name from the local name table, or from the local + * * cache. + * *) + * + * IF name exists in the local name table THEN + * BEGIN + * delete name from local name table; + * inform user that name has been deleted; + * END + * NAME REGISTRATION REQUEST (UNIQUE): + * IF name exists in local name table THEN + * send NEGATIVE NAME REGISTRATION RESPONSE ; + * NAME REGISTRATION REQUEST (GROUP): + * IF name exists in local name table THEN + * BEGIN + * IF local entry is a unique name THEN + * send NEGATIVE NAME REGISTRATION RESPONSE ; + * END + * END (* case *) + * END (* procedure *) + */ +static void +smb_name_process_Mnode_packet(struct name_packet *packet, + struct addr_entry *addr) +{ + if (packet->info & NAME_NM_FLAGS_B) + smb_name_process_Bnode_packet(packet, addr); + else + smb_name_process_Pnode_packet(packet, addr); +} + +static void +smb_name_process_Hnode_packet(struct name_packet *packet, + struct addr_entry *addr) +{ + if (packet->info & NAME_NM_FLAGS_B) + smb_name_process_Bnode_packet(packet, addr); + else + smb_name_process_Pnode_packet(packet, addr); +} + + +/* + * smb_netbios_name_tick + * + * Called once a second to handle name server timeouts. + */ +void +smb_netbios_name_tick(void) +{ + struct name_entry *name; + struct name_entry *entry; + + (void) mutex_lock(&refresh_queue.mtx); + smb_netbios_cache_refresh(&refresh_queue); + + while ((name = refresh_queue.head.forw) != &refresh_queue.head) { + QUEUE_CLIP(name); + if (IS_LOCAL(name->attributes)) { + if (IS_UNIQUE(name->attributes)) { + (void) smb_name_Pnode_refresh_name(name); + } + } else { + entry = smb_name_find_name(name); + smb_name_unlock_name(entry); + } + free(name); + } + (void) mutex_unlock(&refresh_queue.mtx); + + smb_netbios_cache_reset_ttl(); +} + + +/* + * smb_name_find_name + * + * Lookup name cache for the given name. + * If it's not in the cache it'll send a + * name query request and then lookup the + * cache again. Note that if a name is + * returned it's locked and called MUST + * unlock it by calling smb_name_unlock_name() + */ +struct name_entry * +smb_name_find_name(struct name_entry *name) +{ + struct name_entry *result; + + if ((result = smb_netbios_cache_lookup(name)) == 0) { + switch (smb_node_type) { + case 'B': + (void) smb_name_Bnode_find_name(name); + break; + case 'P': + (void) smb_name_Pnode_find_name(name); + break; + case 'M': + (void) smb_name_Mnode_find_name(name); + break; + case 'H': + default: + (void) smb_name_Hnode_find_name(name); + break; + } + return (smb_netbios_cache_lookup(name)); + } + + return (result); +} + +void +smb_name_unlock_name(struct name_entry *name) +{ + smb_netbios_cache_unlock_entry(name); +} + +int +smb_name_add_name(struct name_entry *name) +{ + int rc = 1; + + smb_netbios_name_dump(name); + + switch (smb_node_type) { + case 'B': + rc = smb_name_Bnode_add_name(name); + break; + case 'P': + rc = smb_name_Pnode_add_name(name); + break; + case 'M': + rc = smb_name_Mnode_add_name(name); + break; + case 'H': + default: + rc = smb_name_Hnode_add_name(name); + break; + } + + if (rc >= 0) + (void) smb_netbios_cache_insert(name); + + return (rc); +} + +int +smb_name_delete_name(struct name_entry *name) +{ + int rc; + unsigned char type; + + type = name->name[15]; + if ((type != 0x00) && (type != 0x20)) { + syslog(LOG_ERR, + "netbios: error trying to delete non-local name"); + smb_netbios_name_logf(name); + name->attributes &= ~NAME_ATTR_LOCAL; + return (-1); + } + + smb_netbios_cache_delete(name); + + switch (smb_node_type) { + case 'B': + rc = smb_name_Bnode_delete_name(name); + break; + case 'P': + rc = smb_name_Pnode_delete_name(name); + break; + case 'M': + rc = smb_name_Mnode_delete_name(name); + break; + case 'H': + default: + rc = smb_name_Hnode_delete_name(name); + break; + } + + if (rc > 0) + return (0); + + return (-1); +} + +typedef struct { + struct addr_entry *addr; + char *buf; + int length; +} worker_param_t; + +/* + * smb_netbios_worker + * + * Process incoming request/response packets for Netbios + * name service (on port 138). + */ +void * +smb_netbios_worker(void *arg) +{ + worker_param_t *p = (worker_param_t *)arg; + struct addr_entry *addr = p->addr; + struct name_packet *packet; + + if ((packet = smb_name_buf_to_packet(p->buf, p->length)) != 0) { + if (packet->info & NAME_OPCODE_R) { + /* Reply packet */ + smb_reply_ready(packet, addr); + free(p->buf); + free(p); + return (0); + } + + /* Request packet */ + switch (smb_node_type) { + case 'B': + smb_name_process_Bnode_packet(packet, addr); + break; + case 'P': + smb_name_process_Pnode_packet(packet, addr); + break; + case 'M': + smb_name_process_Mnode_packet(packet, addr); + break; + case 'H': + default: + smb_name_process_Hnode_packet(packet, addr); + break; + } + + if (packet->answer) + smb_netbios_name_freeaddrs(packet->answer->name); + free(packet); + } + else + { + syslog(LOG_DEBUG, "SmbNBNS: error decoding received packet"); + } + + free(addr); + free(p->buf); + free(p); + return (0); +} + +static void +smb_netbios_wins_config(char *ip) +{ + uint32_t ipaddr; + + ipaddr = inet_addr(ip); + if (ipaddr != INADDR_NONE) { + smb_nbns[nbns_num].flags = ADDR_FLAG_VALID; + smb_nbns[nbns_num].sinlen = sizeof (struct sockaddr_in); + smb_nbns[nbns_num].sin.sin_family = AF_INET; + smb_nbns[nbns_num].sin.sin_addr.s_addr = ipaddr; + smb_nbns[nbns_num++].sin.sin_port = + htons(NAME_SERVICE_UDP_PORT); + smb_node_type = 'H'; + } +} + +void +smb_netbios_name_config(void) +{ + uint32_t ipaddr; + struct name_entry name; + char myname[MAXHOSTNAMELEN]; + int i; + int smb_nc_cnt; + net_cfg_t cfg; + + if (smb_getnetbiosname(myname, MAXHOSTNAMELEN) != 0) + return; + + /* Start with no broadcast addresses */ + bcast_num = 0; + bzero(smb_bcast_list, sizeof (addr_entry_t) * SMB_PI_MAX_NETWORKS); + + smb_nc_cnt = smb_nic_get_num(); + /* Add all of my broadcast addresses */ + for (i = 0; i < smb_nc_cnt; i++) { + if (smb_nic_get_byind(i, &cfg) == NULL) { + break; + } + smb_bcast_list[bcast_num].flags = ADDR_FLAG_VALID; + smb_bcast_list[bcast_num].attributes = NAME_ATTR_LOCAL; + smb_bcast_list[bcast_num].sinlen = sizeof (struct sockaddr_in); + smb_bcast_list[bcast_num].sin.sin_family = AF_INET; + smb_bcast_list[bcast_num].sin.sin_port = + htons(NAME_SERVICE_UDP_PORT); + smb_bcast_list[bcast_num++].sin.sin_addr.s_addr = + cfg.broadcast; + } + + /* Start with no WINS */ + smb_node_type = 'B'; + nbns_num = 0; + bzero(smb_nbns, sizeof (addr_entry_t) * SMB_PI_MAX_WINS); + + /* add any configured WINS */ + smb_config_rdlock(); + smb_netbios_wins_config(smb_config_getstr(SMB_CI_WINS_SRV1)); + smb_netbios_wins_config(smb_config_getstr(SMB_CI_WINS_SRV2)); + smb_config_unlock(); + + for (i = 0; i < smb_nc_cnt; i++) { + if (smb_nic_get_byind(i, &cfg) == NULL) { + break; + } + if (cfg.exclude) + continue; + + ipaddr = cfg.ip; + smb_init_name_struct((unsigned char *)myname, 0x00, 0, ipaddr, + htons(DGM_SRVC_UDP_PORT), NAME_ATTR_UNIQUE, + NAME_ATTR_LOCAL, &name); + (void) smb_name_add_name(&name); + + smb_init_name_struct((unsigned char *)myname, 0x20, 0, + ipaddr, htons(DGM_SRVC_UDP_PORT), + NAME_ATTR_UNIQUE, NAME_ATTR_LOCAL, &name); + (void) smb_name_add_name(&name); + } +} + +void +smb_netbios_name_unconfig(void) +{ + struct name_entry *name; + + (void) mutex_lock(&delete_queue.mtx); + smb_netbios_cache_delete_locals(&delete_queue); + + while ((name = delete_queue.head.forw) != &delete_queue.head) { + QUEUE_CLIP(name); + (void) smb_name_delete_name(name); + free(name); + } + (void) mutex_unlock(&delete_queue.mtx); +} + +void +smb_netbios_name_reconfig(void) +{ + smb_netbios_name_unconfig(); + smb_netbios_name_config(); +} + +/* + * process_incoming Function: void smb_netbios_name_service_daemon(void) + * + * Description: + * + * Put test description here. + * + * Inputs: + * Nothing + * + * Returns: + * int -> Description + */ +/*ARGSUSED*/ +void * +smb_netbios_name_service_daemon(void *arg) +{ + struct sockaddr_in sin; + struct addr_entry *addr; + int len; + int flag = 1; + char *buf; + worker_param_t *worker_param; + net_cfg_t cfg; + + /* + * Initialize reply_queue + */ + bzero(&reply_queue, sizeof (reply_queue)); + reply_queue.forw = reply_queue.back = &reply_queue; + + if (!smb_netbios_cache_init()) + return (0); + + bcast_num = 0; + bzero(smb_bcast_list, sizeof (addr_entry_t) * SMB_PI_MAX_NETWORKS); + + if ((name_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + syslog(LOG_ERR, + "smbd: Could not create AF_INET, SOCK_DGRAM, socket"); + smb_netbios_cache_fini(); + smb_netbios_chg_status(NETBIOS_NAME_SVC_FAILED, 1); + return (0); + } + + (void) setsockopt(name_sock, SOL_SOCKET, SO_BROADCAST, &flag, + sizeof (flag)); + + bzero(&sin, sizeof (struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_port = htons(NAME_SERVICE_UDP_PORT); + if (bind(name_sock, (struct sockaddr *)&sin, sizeof (sin)) != 0) { + syslog(LOG_ERR, + "smbd: Bind to name service port %d failed (%d)", + NAME_SERVICE_UDP_PORT, errno); + smb_netbios_cache_fini(); + (void) close(name_sock); + smb_netbios_chg_status(NETBIOS_NAME_SVC_FAILED, 1); + return (0); + } + + smb_netbios_chg_status(NETBIOS_NAME_SVC_RUNNING, 1); + + while (((nb_status.state & NETBIOS_SHUTTING_DOWN) == 0) || + (nb_status.state & NETBIOS_BROWSER_RUNNING)) { + if ((buf = malloc(MAX_DATAGRAM_LENGTH)) == 0) { + /* Sleep for 10 sec and try again */ + (void) sleep(10); + continue; + } + if ((addr = (struct addr_entry *) + malloc(sizeof (struct addr_entry))) == 0) { + /* Sleep for 10 sec and try again */ + free(buf); + (void) sleep(10); + continue; + } +ignore: bzero(addr, sizeof (struct addr_entry)); + addr->sinlen = sizeof (addr->sin); + addr->forw = addr->back = addr; + + if ((len = recvfrom(name_sock, buf, MAX_DATAGRAM_LENGTH, + 0, (struct sockaddr *)&addr->sin, &addr->sinlen)) < 0) { + if (errno == ENOMEM || errno == ENFILE || + errno == EMFILE) { + /* Sleep for 10 sec and try again */ + free(buf); + free(addr); + (void) sleep(10); + continue; + } + syslog(LOG_ERR, + "smbd: NETBIOS name service - recvfrom failed"); + free(buf); + free(addr); + smb_netbios_chg_status(NETBIOS_NAME_SVC_FAILED, 1); + goto shutdown; + } + + /* Ignore any incoming packets from myself... */ + if (smb_nic_get_byip(addr->sin.sin_addr.s_addr, + &cfg) != NULL) { + goto ignore; + } + + /* + * Launch a netbios worker to process the received packet. + */ + worker_param = (worker_param_t *) + malloc(sizeof (worker_param_t)); + if (worker_param) { + pthread_t worker; + pthread_attr_t tattr; + + worker_param->addr = addr; + worker_param->buf = buf; + worker_param->length = len; + + (void) pthread_attr_init(&tattr); + (void) pthread_attr_setdetachstate(&tattr, + PTHREAD_CREATE_DETACHED); + (void) pthread_create(&worker, &tattr, + smb_netbios_worker, worker_param); + (void) pthread_attr_destroy(&tattr); + } + } + +shutdown: + smb_netbios_chg_status(NETBIOS_NAME_SVC_RUNNING, 0); + + (void) mutex_lock(&nb_status.mtx); + while (nb_status.state & NETBIOS_BROWSER_RUNNING) + (void) cond_wait(&nb_status.cv, &nb_status.mtx); + (void) mutex_unlock(&nb_status.mtx); + + if ((nb_status.state & NETBIOS_NAME_SVC_FAILED) == 0) { + /* this might delay shutdown, do we want to do this? */ + /* + * it'll send name release requests but nobody's waiting + * for response and it'll eventually timeout. + */ + smb_netbios_name_unconfig(); + } + (void) close(name_sock); + smb_netbios_cache_fini(); + syslog(LOG_DEBUG, "smbd: Netbios Name Service is down\n"); + return (0); +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c new file mode 100644 index 0000000000..d74bcb168d --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_netlogon.c @@ -0,0 +1,643 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module handles the primary domain controller location protocol. + * The document claims to be version 1.15 of the browsing protocol. It also + * claims to specify the mailslot protocol. + * + * The NETLOGON protocol uses \MAILSLOT\NET mailslots. The protocol + * specification is incomplete, contains errors and is out-of-date but + * it does provide some useful background information. The document + * doesn't mention the NETLOGON_SAMLOGON version of the protocol. + */ + +#include <stdlib.h> +#include <syslog.h> +#include <alloca.h> +#include <arpa/inet.h> +#include <resolv.h> + +#include <smbsrv/mailslot.h> +#include <smbsrv/libsmbns.h> +#include <smbns_ads.h> +#include <smbns_browser.h> +#include <smbns_netbios.h> + +static void smb_netlogon_query(struct name_entry *server, char *mailbox, + char *domain); + +static void smb_netlogon_samlogon(struct name_entry *server, char *mailbox, + char *domain); + +static void smb_netlogon_send(struct name_entry *name, char *domain, + unsigned char *buffer, int count); + +static void smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr); +static int better_dc(uint32_t cur_ip, uint32_t new_ip); + +static char resource_domain[SMB_PI_MAX_DOMAIN]; + +/* + * smb_netlogon_request + * + * This is the entry point locating the resource domain PDC. A netlogon + * request is sent using the specified protocol on the specified network. + * Note that we need to know the domain SID in order to use the samlogon + * format. + * + * Netlogon responses are received asynchronously and eventually handled + * in smb_netlogon_receive. + */ +void +smb_netlogon_request(int net, int protocol, char *domain) +{ + struct name_entry *server; + nt_domain_t *ntdp; + + server = smb_browser_get_srvname(net); + if (server == 0) + return; + + (void) strlcpy(resource_domain, domain, + sizeof (resource_domain)); + + if (strlen(resource_domain) > 0) { + ntdp = nt_domain_lookup_name(resource_domain); + if (protocol == NETLOGON_PROTO_SAMLOGON && ntdp) + smb_netlogon_samlogon(server, + MAILSLOT_NETLOGON_SAMLOGON_RDC, + resource_domain); + else + smb_netlogon_query(server, + MAILSLOT_NETLOGON_RDC, + resource_domain); + } +} + +/* + * smb_netlogon_receive + * + * This is where we handle all incoming NetLogon messages. Currently, we + * ignore requests from anyone else. We are only interested in responses + * to our own requests. The NetLogonResponse provides the name of the PDC. + * If we don't already have a controller name, we use the name provided + * in the message. Otherwise we use the name already in the environment. + */ +void +smb_netlogon_receive(struct datagram *datagram, + char *mailbox, + unsigned char *data, + int datalen) +{ + struct netlogon_opt { + char *mailslot; + void (*handler)(); + } netlogon_opt[] = { + { MAILSLOT_NETLOGON_RDC, smb_netlogon_rdc_rsp }, + { MAILSLOT_NETLOGON_SAMLOGON_RDC, smb_netlogon_rdc_rsp }, + }; + + smb_msgbuf_t mb; + unsigned short opcode; + char src_name[SMB_PI_MAX_HOST]; + mts_wchar_t unicode_src_name[SMB_PI_MAX_HOST]; + unsigned int cpid = oem_get_smb_cpid(); + uint32_t src_ipaddr; + char *junk; + char *primary; + char *domain; + int i; + char ipstr[16]; + int rc; + + src_ipaddr = datagram->src.addr_list.sin.sin_addr.s_addr; + + /* + * The datagram->src.name is in oem codepage format. + * Therefore, we need to convert it to unicode and + * store it in multi-bytes format. + */ + (void) oemstounicodes(unicode_src_name, (char *)datagram->src.name, + SMB_PI_MAX_HOST, cpid); + (void) mts_wcstombs(src_name, unicode_src_name, SMB_PI_MAX_HOST); + + (void) trim_whitespace(src_name); + + (void) inet_ntop(AF_INET, (const void *)(&src_ipaddr), ipstr, + sizeof (ipstr)); + syslog(LOG_DEBUG, "NetLogonReceive: src=%s [%s], mbx=%s", + src_name, ipstr, mailbox); + + smb_msgbuf_init(&mb, data, datalen, 0); + + if (smb_msgbuf_decode(&mb, "w", &opcode) < 0) { + syslog(LOG_ERR, "NetLogonReceive: decode error"); + smb_msgbuf_term(&mb); + return; + } + + switch (opcode) { + case LOGON_PRIMARY_RESPONSE: + /* + * Message contains: + * PDC name (MBS), PDC name (Unicode), Domain name (unicode) + */ + rc = smb_msgbuf_decode(&mb, "sUU", &junk, &primary, &domain); + if (rc < 0) { + syslog(LOG_ERR, + "NetLogonResponse: opcode %d decode error", + opcode); + smb_msgbuf_term(&mb); + return; + } + break; + + case LOGON_SAM_LOGON_RESPONSE: + case LOGON_SAM_USER_UNKNOWN: + /* + * Message contains: + * PDC name, User name, Domain name (all unicode) + */ + rc = smb_msgbuf_decode(&mb, "UUU", &primary, &junk, &domain); + if (rc < 0) { + syslog(LOG_ERR, + "NetLogonResponse: opcode %d decode error", + opcode); + smb_msgbuf_term(&mb); + return; + } + + /* + * skip past the "\\" prefix + */ + primary += strspn(primary, "\\"); + break; + + default: + /* + * We don't respond to PDC discovery requests. + */ + syslog(LOG_DEBUG, "NetLogonReceive: opcode 0x%04x", opcode); + smb_msgbuf_term(&mb); + return; + } + + if (domain == 0 || primary == 0) { + syslog(LOG_ERR, "NetLogonResponse: malformed packet"); + smb_msgbuf_term(&mb); + return; + } + + syslog(LOG_DEBUG, "DC Offer Dom=%s PDC=%s From=%s", + domain, primary, src_name); + + if (strcasecmp(domain, resource_domain)) { + syslog(LOG_DEBUG, "NetLogonResponse: other domain " + "%s, requested %s", domain, resource_domain); + smb_msgbuf_term(&mb); + return; + } + + for (i = 0; i < sizeof (netlogon_opt)/sizeof (netlogon_opt[0]); ++i) { + if (strcasecmp(netlogon_opt[i].mailslot, mailbox) == 0) { + syslog(LOG_DEBUG, "NetLogonReceive: %s", mailbox); + (*netlogon_opt[i].handler)(primary, src_ipaddr); + smb_msgbuf_term(&mb); + return; + } + } + + syslog(LOG_DEBUG, "NetLogonReceive[%s]: unknown mailslot", mailbox); + smb_msgbuf_term(&mb); +} + + + +/* + * smb_netlogon_query + * + * Build and send a LOGON_PRIMARY_QUERY to the MAILSLOT_NETLOGON. At some + * point we should receive a LOGON_PRIMARY_RESPONSE in the mailslot we + * specify in the request. + * + * struct NETLOGON_QUERY { + * unsigned short Opcode; # LOGON_PRIMARY_QUERY + * char ComputerName[]; # ASCII hostname. The response + * # is sent to <ComputerName>(00). + * char MailslotName[]; # MAILSLOT_NETLOGON + * char Pad[]; # Pad to short + * wchar_t ComputerName[] # UNICODE hostname + * DWORD NT_Version; # 0x00000001 + * WORD LmNTToken; # 0xffff + * WORD Lm20Token; # 0xffff + * }; + */ +static void +smb_netlogon_query(struct name_entry *server, + char *mailbox, + char *domain) +{ + smb_msgbuf_t mb; + int offset, announce_len, data_length, name_lengths; + unsigned char buffer[MAX_DATAGRAM_LENGTH]; + char hostname[MAXHOSTNAMELEN]; + + if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0) + return; + + name_lengths = strlen(mailbox)+1+strlen(hostname)+1; + + /* + * The (name_lengths & 1) part is to word align the name_lengths + * before the wc equiv strlen and the "+ 2" is to cover the two + * zero bytes that terminate the wchar string. + */ + data_length = sizeof (short) + name_lengths + (name_lengths & 1) + + mts_wcequiv_strlen(hostname) + 2 + sizeof (long) + sizeof (short) + + sizeof (short); + + offset = smb_browser_load_transact_header(buffer, + sizeof (buffer), data_length, ONE_WAY_TRANSACTION, + MAILSLOT_NETLOGON); + + if (offset < 0) + return; + + smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0); + + announce_len = smb_msgbuf_encode(&mb, "wssUlww", + (short)LOGON_PRIMARY_QUERY, + hostname, + mailbox, + hostname, + 0x1, + 0xffff, + 0xffff); + + if (announce_len <= 0) { + smb_msgbuf_term(&mb); + syslog(LOG_ERR, "NetLogonQuery: encode error"); + return; + } + + smb_netlogon_send(server, domain, buffer, offset + announce_len); + smb_msgbuf_term(&mb); +} + + +/* + * smb_netlogon_samlogon + * + * The SamLogon version of the NetLogon request uses the workstation trust + * account and, I think, may be a prerequisite to the challenge/response + * netr authentication. The trust account username is the hostname with a + * $ appended. The mailslot for this request is MAILSLOT_NTLOGON. At some + * we should receive a LOGON_SAM_LOGON_RESPONSE in the mailslot we + * specify in the request. + * + * struct NETLOGON_SAM_LOGON { + * unsigned short Opcode; # LOGON_SAM_LOGON_REQUEST + * unsigned short RequestCount; # 0 + * wchar_t UnicodeComputerName; # hostname + * wchar_t UnicodeUserName; # hostname$ + * char *MailslotName; # response mailslot + * DWORD AllowableAccountControlBits; # 0x80 = WorkstationTrustAccount + * DWORD DomainSidSize; # domain sid length in bytes + * BYTE *DomainSid; # domain sid + * uint32_t NT_Version; # 0x00000001 + * unsigned short LmNTToken; # 0xffff + * unsigned short Lm20Token; # 0xffff + * }; + */ +static void +smb_netlogon_samlogon(struct name_entry *server, + char *mailbox, + char *domain) +{ + smb_msgbuf_t mb; + nt_domain_t *ntdp; + nt_sid_t *domain_sid; + unsigned domain_sid_len; + char *username; + unsigned char buffer[MAX_DATAGRAM_LENGTH]; + int offset; + int announce_len; + int data_length; + int name_length; + char hostname[MAXHOSTNAMELEN]; + + syslog(LOG_DEBUG, "NetLogonSamLogonReq: %s", domain); + + if ((ntdp = nt_domain_lookup_name(domain)) == 0) { + syslog(LOG_ERR, "NetLogonSamLogonReq[%s]: no sid", domain); + return; + } + + domain_sid = ntdp->sid; + domain_sid_len = nt_sid_length(domain_sid); + nt_sid_logf(domain_sid); + + if (smb_gethostname(hostname, MAXHOSTNAMELEN, 1) != 0) + return; + + /* + * The username will be the trust account name on the PDC. + */ + name_length = strlen(hostname) + 2; + username = alloca(name_length); + (void) snprintf(username, name_length, "%s$", hostname); + + /* + * Add 2 to wide-char equivalent strlen to cover the + * two zero bytes that terminate the wchar string. + */ + name_length = strlen(mailbox)+1; + + data_length = sizeof (short) + + sizeof (short) + + mts_wcequiv_strlen(hostname) + 2 + + mts_wcequiv_strlen(username) + 2 + + name_length + + sizeof (long) + + sizeof (long) + + domain_sid_len + 3 /* padding */ + + sizeof (long) + + sizeof (short) + + sizeof (short); + + offset = smb_browser_load_transact_header(buffer, + sizeof (buffer), data_length, ONE_WAY_TRANSACTION, + MAILSLOT_NTLOGON); + + if (offset < 0) { + syslog(LOG_ERR, "NetLogonSamLogonReq: header error"); + return; + } + + /* + * The domain SID is padded with 3 leading zeros. + */ + smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0); + announce_len = smb_msgbuf_encode(&mb, "wwUUsll3.#clww", + (short)LOGON_SAM_LOGON_REQUEST, + 0, /* RequestCount */ + hostname, /* UnicodeComputerName */ + username, /* UnicodeUserName */ + mailbox, /* MailslotName */ + 0x00000080, /* AllowableAccountControlBits */ + domain_sid_len, /* DomainSidSize */ + domain_sid_len, domain_sid, /* DomainSid */ + 0x00000001, /* NT_Version */ + 0xffff, /* LmNTToken */ + 0xffff); /* Lm20Token */ + + if (announce_len <= 0) { + syslog(LOG_ERR, "NetLogonSamLogonReq: encode error"); + smb_msgbuf_term(&mb); + return; + } + + smb_netlogon_send(server, domain, buffer, offset + announce_len); + smb_msgbuf_term(&mb); +} + + +/* + * Send a query for each version of the protocol. + */ +static void +smb_netlogon_send(struct name_entry *name, + char *domain, + unsigned char *buffer, + int count) +{ + static char suffix[] = { 0x1B, 0x1C }; + struct name_entry dname; + struct name_entry *dest; + struct name_entry *dest_dup; + int i; + + for (i = 0; i < sizeof (suffix)/sizeof (suffix[0]); i++) { + smb_init_name_struct((unsigned char *)domain, suffix[i], + 0, 0, 0, 0, 0, &dname); + + syslog(LOG_DEBUG, "smb_netlogon_send"); + smb_netbios_name_dump(&dname); + if ((dest = smb_name_find_name(&dname)) != 0) { + dest_dup = smb_netbios_name_dup(dest, 1); + smb_name_unlock_name(dest); + if (dest_dup) { + (void) smb_netbios_datagram_send(name, dest_dup, + buffer, count); + free(dest_dup); + } + } else { + syslog(LOG_DEBUG, "smbd: NBNS couldn't find %s<0x%X>", + domain, suffix[i]); + } + } +} + +/* + * smb_netlogon_rdc_rsp + * + * This is where we process netlogon responses for the resource domain. + * The src_name is the real name of the remote machine. + */ +static void +smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr) +{ + static int initialized = 0; + smb_ntdomain_t *pi; + uint32_t ipaddr; + uint32_t prefer_ipaddr = 0; + char ipstr[16]; + char srcip[16]; + char *p; + int rc; + + (void) inet_ntop(AF_INET, (const void *)(&src_ipaddr), + srcip, sizeof (srcip)); + + smb_config_rdlock(); + if ((p = smb_config_get(SMB_CI_DOMAIN_SRV)) != 0) { + rc = inet_pton(AF_INET, p, &prefer_ipaddr); + if (rc == 0) + prefer_ipaddr = 0; + + if (!initialized) { + (void) inet_ntop(AF_INET, + (const void *)(&prefer_ipaddr), + ipstr, sizeof (ipstr)); + syslog(LOG_DEBUG, "SMB DC Preference: %s", ipstr); + initialized = 1; + } + } + smb_config_unlock(); + + syslog(LOG_DEBUG, "DC Offer [%s]: %s [%s]", + resource_domain, src_name, srcip); + + if ((pi = smb_getdomaininfo(0)) != 0) { + if (prefer_ipaddr != 0 && prefer_ipaddr == pi->ipaddr) { + syslog(LOG_DEBUG, "DC for %s: %s [%s]", + resource_domain, src_name, srcip); + return; + } + + ipaddr = pi->ipaddr; + } else + ipaddr = 0; + + if (better_dc(ipaddr, src_ipaddr) || + (prefer_ipaddr != 0 && prefer_ipaddr == src_ipaddr)) { + smb_setdomaininfo(resource_domain, src_name, + src_ipaddr); + syslog(LOG_DEBUG, "DC discovered for %s: %s [%s]", + resource_domain, src_name, srcip); + } +} + +static int +better_dc(uint32_t cur_ip, uint32_t new_ip) +{ + net_cfg_t cfg; + + /* + * If we don't have any current DC, + * then use the new one of course. + */ + if (cur_ip == 0) + return (1); + + if (smb_nic_get_bysubnet(cur_ip, &cfg) != NULL) + return (0); + if (smb_nic_get_bysubnet(new_ip, &cfg) != NULL) + return (1); + /* + * Otherwise, just keep the old one. + */ + return (0); +} + +/* + * msdcs_lookup_ads + * + * Try to find a domain controller in ADS. Actually we want to query DNS + * but we need to find out if ADS is enabled and this is probably the + * best way. The IP address isn't set up in the ADS_HANDLE so we need to + * make the ads_find_host call. This will only succeed if ADS is enabled. + * + * Returns 1 if a domain controller was found and its name and IP address + * have been updated. Otherwise returns 0. + */ +int +msdcs_lookup_ads(void) +{ + ADS_HOST_INFO *hinfo = 0; + int ads_port = 0; + char ads_domain[MAXHOSTNAMELEN]; + char site_service[MAXHOSTNAMELEN]; + char service[MAXHOSTNAMELEN]; + char *site; + char *p; + char *ip_addr; + struct in_addr ns_list[MAXNS]; + int i, cnt, go_next; + + if (smb_getdomainname(ads_domain, MAXHOSTNAMELEN) != 0) + return (0); + + /* + * Initialize the NT domain name. + */ + (void) strlcpy(resource_domain, ads_domain, SMB_PI_MAX_DOMAIN); + if ((p = strchr(resource_domain, '.')) != 0) + *p = '\0'; + + smb_config_rdlock(); + if (smb_config_getyorn(SMB_CI_MSDCS_DISABLE) != 0) { + /* + * The system administrator doesn't + * want to use ADS to find the PDC. + */ + syslog(LOG_DEBUG, "msdcsLookupADS: disabled"); + smb_config_unlock(); + return (0); + } + site = smb_config_getstr(SMB_CI_ADS_SITE); + smb_config_unlock(); + + syslog(LOG_DEBUG, "msdcsLookupADS %s, MAXHOSTNAMELEN=%d", + ads_domain, MAXHOSTNAMELEN); + if (site && *site != 0) { + (void) snprintf(site_service, MAXHOSTNAMELEN, + "_ldap._tcp.%s._sites.dc._msdcs.%s", + site, ads_domain); + } + + (void) snprintf(service, MAXHOSTNAMELEN, + "_ldap._tcp.dc._msdcs.%s", ads_domain); + + cnt = smb_get_nameservers(ns_list, MAXNS); + + go_next = 0; + for (i = 0; i < cnt; i++) { + ip_addr = inet_ntoa(ns_list[i]); + + hinfo = ads_find_host(ip_addr, ads_domain, &ads_port, + site_service, &go_next); + + if (hinfo == NULL) { + hinfo = ads_find_host(ip_addr, ads_domain, &ads_port, + service, &go_next); + } + + if ((hinfo != NULL) || (go_next == 0)) + break; + } + + if (hinfo == NULL) { + syslog(LOG_DEBUG, "msdcsLookupADS: unable to find host"); + return (0); + } + + syslog(LOG_DEBUG, "msdcsLookupADS: %s [%I]", hinfo->name, + hinfo->ip_addr); + + /* + * Remove the domain extension - the + * NetBIOS browser can't handle it. + */ + if ((p = strchr(hinfo->name, '.')) != 0) + *p = '\0'; + + smb_netlogon_rdc_rsp(hinfo->name, hinfo->ip_addr); + + return (1); +} diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_nicconfig.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_nicconfig.c new file mode 100644 index 0000000000..0aaf8338b4 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_nicconfig.c @@ -0,0 +1,1163 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> +#include <libintl.h> +#include <strings.h> +#include <unistd.h> +#include <synch.h> +#include <stropts.h> +#include <errno.h> +#include <pthread.h> + +#include <inet/ip.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netdb.h> +#include <net/route.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <resolv.h> + +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/systeminfo.h> + +#include <smbsrv/libsmbns.h> + +static int smb_nic_get_list(struct if_list **); +static void smb_nic_clear_if_list(struct if_list *); + +/* This is the list we will monitor */ +static net_cfg_list_t smb_nic_list = { NULL, 0 }; +static pthread_mutex_t smb_nic_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t smb_ns_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* Nameserver information */ +static struct __res_state smb_res; + +void +smb_resolver_init(void) +{ + int ret; + (void) pthread_mutex_lock(&smb_ns_mutex); + ret = res_ninit(&smb_res); + (void) pthread_mutex_unlock(&smb_ns_mutex); + if (ret < 0) { + syslog(LOG_ERR, "Failed to initialize resolver lib"); + } +} + +void +smb_resolver_close(void) +{ + (void) pthread_mutex_lock(&smb_ns_mutex); + res_nclose(&smb_res); + (void) pthread_mutex_unlock(&smb_ns_mutex); +} + +int +smb_get_nameservers(struct in_addr *ips, int sz) +{ + union res_sockaddr_union set[MAXNS]; + int i, cnt; + + if (ips == NULL) + return (0); + (void) pthread_mutex_lock(&smb_ns_mutex); + cnt = res_getservers(&smb_res, set, MAXNS); + for (i = 0; i < cnt; i++) { + if (i >= sz) + break; + ips[i] = set[i].sin.sin_addr; + syslog(LOG_DEBUG, "NS Found %s name server\n", + inet_ntoa(ips[i])); + } + syslog(LOG_DEBUG, "NS Found %d name servers\n", i); + (void) pthread_mutex_unlock(&smb_ns_mutex); + return (i); +} + +uint16_t +smb_get_next_resid(void) +{ + uint16_t id; + (void) pthread_mutex_lock(&smb_ns_mutex); + id = ++smb_res.id; + (void) pthread_mutex_unlock(&smb_ns_mutex); + return (id); +} + +/* + * The common NIC library will provide functions to obtain information + * on all interfaces. Information will include IP addresses, netmasks + * and broadcast address, as well as network statistic details. + */ + +/* + * Return IP string address associated with interface argument. + * If an error occurs, -1 will be returned. + * A return value of 1 indicates an unconfigured IP address + */ +static int +smb_nic_get_ip_addr(char *interface, char *IP, unsigned int IP_length) +{ + struct lifreq lifrr; + struct sockaddr_in *sa; + int sfd; + + sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + + if (sfd < 0) { + syslog(LOG_ERR, "%s", "nic_get_IP:socket open failed\n"); + return (-1); + } + + (void) strncpy(lifrr.lifr_name, interface, sizeof (lifrr.lifr_name)); + + if (ioctl(sfd, SIOCGLIFADDR, &lifrr) < 0) { + syslog(LOG_ERR, "%s", "nic_get_IP: get IP address failed\n"); + (void) close(sfd); + return (-1); + } + /* Test length of allocated memory to avoid buffer overflow */ + if (IP_length < SIZE_IP) { + syslog(LOG_ERR, "%s", "nic_get_IP: insufficient memory" + "allocation\n"); + (void) close(sfd); + return (-1); + } + sa = (struct sockaddr_in *) &lifrr.lifr_addr; + (void) strncpy(IP, inet_ntoa(sa->sin_addr), SIZE_IP); + /* Check for unconfigured interface */ + if (strncmp(IP, "0.0.0.0", sizeof (IP)) == 0) { + syslog(LOG_ERR, "%s", "nic_get_IP: unconfigured interface\n"); + (void) close(sfd); + return (1); + } + (void) close(sfd); + return (0); +} + +/* + * Return IP address associated with interface argument. If an error occurs, + * -1 will be returned. A return value of 1 indicates an unconfigured IP + * address + */ +int +smb_nic_get_IP(char *interface, uint32_t *uip) +{ + struct lifreq lifrr; + struct sockaddr_in *sa; + int sfd; + + sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + + if (sfd < 0) { + syslog(LOG_ERR, "%s", "nic_get_IP:socket open failed\n"); + return (-1); + } + + (void) strncpy(lifrr.lifr_name, interface, sizeof (lifrr.lifr_name)); + + if (ioctl(sfd, SIOCGLIFADDR, &lifrr) < 0) { + syslog(LOG_ERR, "%s", "nic_get_IP: get IP address failed\n"); + (void) close(sfd); + return (-1); + } + sa = (struct sockaddr_in *) &lifrr.lifr_addr; + if (uip != NULL) + *uip = (uint32_t)sa->sin_addr.s_addr; + (void) close(sfd); + return (0); +} + +/* + * Return broadcast address associated with interface argument.If an error + * occurs, -1 will be returned. A return value of 1 indicates an unconfigured + * broadcast address + */ +int +smb_nic_get_broadcast(char *interface, uint32_t *uip) +{ + struct lifreq lifrr; + struct sockaddr_in *sa; + int sfd; + + sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sfd < 0) { + syslog(LOG_ERR, "%s", "nic_get_broadcast:" + "socket open failed\n"); + return (-1); + } + + (void) strncpy(lifrr.lifr_name, interface, sizeof (lifrr.lifr_name)); + + if (ioctl(sfd, SIOCGLIFBRDADDR, &lifrr) < 0) { + syslog(LOG_ERR, "%s", "nic_get_broadcast:" + "get broadcast address failed\n"); + (void) close(sfd); + return (-1); + } + sa = (struct sockaddr_in *)&lifrr.lifr_broadaddr; + if (uip != NULL) + *uip = (uint32_t)sa->sin_addr.s_addr; + (void) close(sfd); + return (0); + +} + +/* + * Return netmask address associated with interface argument. If error occurs, + * -1 will be returned. A return value of 1 indicates an unconfigured netmask + * address + */ +int +smb_nic_get_netmask(char *interface, uint32_t *uip) +{ + struct lifreq lifrr; + struct sockaddr_in *sa; + int sfd; + + sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sfd < 0) { + syslog(LOG_ERR, "%s", "nic_get_netmask:" + "socket open failed\n"); + return (-1); + } + + (void) strncpy(lifrr.lifr_name, interface, sizeof (lifrr.lifr_name)); + + if (ioctl(sfd, SIOCGLIFNETMASK, &lifrr) < 0) { + syslog(LOG_ERR, "%s", "nic_get_netmask:" + "get netmask address failed\n"); + (void) close(sfd); + return (-1); + } + sa = (struct sockaddr_in *)&lifrr.lifr_addr; + if (uip != NULL) + *uip = (uint32_t)sa->sin_addr.s_addr; + (void) close(sfd); + return (0); + +} + +/* + * Fill ip_alias with IP addresses if any + * If it returns 0, there are no associated aliases with the interface. + * If it returns -1, there was an error + * If it returns 1, there are associated IP aliases with the interface. + */ +int +smb_nic_get_IP_aliases(char *interface, struct ip_alias **list) +{ + char ** names = NULL; + int result = 0; + int numnics, i, ret = 0; + char IP[SIZE_IP]; + struct ip_alias *tmp; + + *list = NULL; + + /* If the interface is a logical interface, return immediately */ + if (strchr(interface, ':') != NULL) { + syslog(LOG_ERR, "%s", "nic_get_IP_aliases:" + "invalid physical interface"); + return (ret); + } + + numnics = smb_nic_build_if_name(&names); + + for (i = 0; i < numnics; i++) { + /* + * Compare passed interface name to all other interface names. + * If it matches in the form of :1, it is an associated alias + * Example bge1:1's ip address is an ip alias of bge1 + */ + if (strncasecmp(interface, names[i], strlen(names[0])) == 0 && + strchr(names[i], ':') != 0) { + + result = smb_nic_get_ip_addr(names[i], + IP, sizeof (IP)); + if (result == -1) + return (result); + + tmp = (struct ip_alias *)malloc( + sizeof (struct ip_alias)); + if (tmp == NULL) { + syslog(LOG_ERR, "%s", "nic_get" + "_IP_aliases: out of memory"); + (void) smb_nic_clear_name_list(names, + numnics); + return (-1); + } + + (void) strncpy(tmp->name, IP, sizeof (tmp->name)); + tmp->next = *list; + *list = tmp; + ret = 1; + } + } + (void) smb_nic_clear_name_list(names, numnics); + return (ret); +} + +/* + * Return number of plumbed interfaces. Loopback interface is ignored + */ +int +smb_nic_get_number(void) +{ + struct lifnum lifn; + int numifs = 0, sfd; + + sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sfd < 0) { + syslog(LOG_ERR, "%s", "nic_get_number:" + "socket open failed"); + return (-1); + } + + lifn.lifn_family = AF_INET; + lifn.lifn_flags = 0; + + if (ioctl(sfd, SIOCGLIFNUM, &lifn) < 0) { + syslog(LOG_ERR, "%s", "nic_get_number:" + "unable to determine number"); + (void) close(sfd); + return (-1); + } + + numifs = lifn.lifn_count - 1; /* loopback */ + (void) close(sfd); + return (numifs); +} + +/* + * Given an interface name, return the name of the group it belongs to. + */ +int +smb_nic_get_group(char *lifname, char *grname) +{ + struct lifreq lifr; + int sfd; + int save_errno; + + sfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sfd < 0) { + syslog(LOG_ERR, "%s", "nic_set_group:socket open failed"); + return (-1); + } + if (strchr(lifname, ':') == NULL) { + (void) memset(lifr.lifr_groupname, 0, + sizeof (lifr.lifr_groupname)); + (void) strncpy(lifr.lifr_name, lifname, + sizeof (lifr.lifr_name)); + if (ioctl(sfd, SIOCGLIFGROUPNAME, (caddr_t)&lifr) >= 0) { + if (strlen(lifr.lifr_groupname) > 0) { + (void) strncpy(grname, lifr.lifr_groupname, + sizeof (lifr.lifr_groupname)); + } + } else { + save_errno = errno; + syslog(LOG_ERR, "smb_nic_get_group: ioctl failed"); + (void) close(sfd); + errno = save_errno; + return (-1); + } + } + (void) close(sfd); + return (0); +} + +/* + * Read the /etc/defaultrouter file for the gateway address. If an error occurs, + * -1 will be returned. + */ +int +smb_nic_get_default_gateway(char *gw, unsigned int gw_length) +{ + FILE *fp; + + fp = fopen(GATEWAY_FILE, "r"); + + if (fp == NULL) { + (void) fclose(fp); + return (-1); + } else { + /* Test length of allocated memory to avoid buffer overflow */ + if (gw_length < SIZE_IP) { + syslog(LOG_ERR, "%s", "get_default_gateway: " + "insufficient memory allocation\n"); + (void) fclose(fp); + return (-1); + } + (void) fgets(gw, SIZE_IP, fp); + (void) fclose(fp); + } + + return (0); +} + +/* + * Build the list of interface names, both physical and logical. + * A pointer to a pointer to a char will be filled with the info + */ +int +smb_nic_build_if_name(char ***if_names) +{ + struct if_list *iflist; + struct if_list *iflistptr; + int num_ifs, i; + + /* Get the interfaces */ + num_ifs = smb_nic_get_list(&iflist); + + /* Build the list of names */ + *if_names = (char **)malloc(sizeof (char *) * num_ifs); + + if (if_names == NULL) { + syslog(LOG_ERR, "%s", "Unable to build interface names"); + return (-1); + } + + for (i = 0, iflistptr = iflist; i < num_ifs; + iflistptr = iflistptr->next, i++) { + (*if_names)[i] = (char *)strdup(iflistptr->name); + } + (void) smb_nic_clear_if_list(iflist); + return (num_ifs); +} + +/* + * Get number of physical interfaces + */ +int +smb_nic_get_num_physical(void) +{ + char **names = NULL; + int phys_ifs = 0; + int i, result = 0; + /* Get list of interface names */ + result = smb_nic_build_if_name(&names); + if (result == -1) { + syslog(LOG_ERR, "%s", "Unable to determine num interfaces"); + return (-1); + } + for (i = 0; i < result; i++) { + if (strchr(names[i], ':') == NULL) { + /* It's a physical interface */ + phys_ifs++; + } + } + (void) smb_nic_clear_name_list(names, result); + return (phys_ifs); +} + +/* + * Get number of logical interfaces + */ +int +smb_nic_get_num_logical(void) +{ + char **names = NULL; + int log_ifs = 0; + int i, result = 0; + /* Get list of interface names */ + result = smb_nic_build_if_name(&names); + if (result == -1) { + syslog(LOG_ERR, "%s", "Unable to determine num interfaces"); + return (-1); + } + for (i = 0; i < result; i++) { + if (strchr(names[i], ':') != NULL) { + /* It's a logical interface */ + log_ifs++; + } + } + (void) smb_nic_clear_name_list(names, result); + return (log_ifs); +} + +/* + * Get number of aliases associated with an interface + */ +int +smb_nic_get_num_aliases(char *interface) +{ + char **names = NULL; + int aliases = 0; + int i, result = 0; + + if (interface == NULL) { + syslog(LOG_ERR, "%s", "Interface name not supplied"); + return (-1); + } + /* Get list of interface names */ + result = smb_nic_build_if_name(&names); + if (result == -1) { + syslog(LOG_ERR, "%s", "Unable to determine num interfaces"); + return (-1); + } + for (i = 0; i < result; i++) { + if (strncasecmp(interface, names[i], strlen(names[0])) == 0 && + strchr(names[i], ':') != 0) { + /* It's an alias */ + aliases++; + } + } + (void) smb_nic_clear_name_list(names, result); + if (aliases == 0) + return (1); /* Minimum of 1 for NULL assignment */ + else + return (aliases); +} + +/* + * Get the list of currently plumbed interface names. The loopback(lo0) + * port is ignored + */ +static int +smb_nic_get_list(struct if_list **list) +{ + int cnt = 0; + struct if_list *tmp, *p; + int n, s; + char *buf; + struct ifconf ifc; + register struct ifreq *ifrp = NULL; + struct ifreq ifr; + int numifs = 0; + unsigned int bufsize = 0; + + *list = NULL; + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) { + syslog(LOG_ERR, "%s", "get_net_list: socket"); + return (-1); + } + + if (ioctl(s, SIOCGIFNUM, (char *)&numifs) < 0) { + syslog(LOG_ERR, "%s", "get number of interfaces"); + return (-1); + } + + bufsize = numifs * sizeof (struct ifreq); + buf = (char *)malloc(bufsize); + if (buf == NULL) { + syslog(LOG_ERR, "%s", "out of memory\n"); + (void) close(s); + return (-1); + } + ifc.ifc_len = bufsize; + ifc.ifc_buf = buf; + + if (ioctl(s, SIOCGIFCONF, (char *)&ifc) < 0) { + syslog(LOG_ERR, "%s", "Unable to get interface list\n"); + (void) close(s); + (void) free(buf); + return (-1); + } + + ifrp = ifc.ifc_req; + for (n = ifc.ifc_len / sizeof (struct ifreq); n > 0; n--, ifrp++) { + /* Get the flags so that we can skip the loopback interface */ + (void) memset((char *)&ifr, '\0', sizeof (ifr)); + (void) strncpy(ifr.ifr_name, ifrp->ifr_name, + sizeof (ifr.ifr_name)); + + if (ioctl(s, SIOCGIFFLAGS, (caddr_t)&ifr) < 0) { + syslog(LOG_ERR, "%s", "unable to determine flags"); + (void) close(s); + (void) free(buf); + return (-1); + } + + if (ifr.ifr_flags & IFF_LOOPBACK) + continue; + if ((ifr.ifr_flags & IFF_UP) == 0) + continue; + tmp = (struct if_list *)malloc(sizeof (struct if_list)); + if (tmp == NULL) { + syslog(LOG_ERR, "%s", "out of memory\n"); + (void) close(s); + (void) free(buf); + return (-1); + } + + tmp->next = NULL; + (void) strncpy(tmp->name, ifrp->ifr_name, sizeof (tmp->name)); + if (*list == NULL) { + *list = tmp; + } else { + for (p = *list; p->next; p = p->next) + ; + p->next = tmp; + } + cnt++; + } + (void) close(s); + (void) free(buf); + return (cnt); +} + +/* + * This will mimick the workings of ifconfig -a command. A net_cfg + * pointer will be passed, and all information will be assigned + * within this function. Memory will be assigned in this function + * also so the user doesn't have to worry about it. Freeing memory + * will be handled in a different function - smb_nic_clear_memory + */ +int +smb_nic_build_network_structures(net_cfg_t **nc, int *number) +{ + char ** names = NULL; + int res, numnics = 0; + int num_aliases = 0; + uint32_t uip; + uint64_t flags; + int i = 0; + int j = 1; + int k = 0; + struct ip_alias *list = NULL; + net_cfg_t *nc_array; + char excludestr[MAX_EXCLUDE_LIST_LEN]; + ipaddr_t exclude[SMB_PI_MAX_NETWORKS]; + int nexclude; + char *winsexclude; + + *number = 0; + numnics = smb_nic_build_if_name(&names); + nc_array = *nc = malloc(sizeof (net_cfg_t) * numnics); + if (nc_array == NULL) { + (void) smb_nic_clear_name_list(names, numnics); + return (-1); + } + bzero(nc_array, sizeof (net_cfg_t) * numnics); + + smb_config_rdlock(); + excludestr[0] = '\0'; + winsexclude = smb_config_getstr(SMB_CI_WINS_EXCL); + if (winsexclude != NULL) + (void) strlcpy(excludestr, winsexclude, sizeof (excludestr)); + smb_config_unlock(); + nexclude = smb_wins_iplist(excludestr, exclude, SMB_PI_MAX_NETWORKS); + + for (i = 0; i < numnics; i++) { + + if (strchr(names[i], ':') == NULL) { + /* Will not provide info on logical interfaces */ + + (void) memset ((*nc), 0, sizeof (net_cfg_t)); + num_aliases = smb_nic_get_num_aliases(names[i]); + if (num_aliases == -1) { + (void) smb_nic_clear_name_list(names, numnics); + free (*nc); + return (-1); + } + + (*nc)->aliases = (char **)malloc( + (sizeof (char) * IP_ABITS) * num_aliases); + if ((*nc)->aliases == NULL) { + (void) smb_nic_clear_name_list(names, numnics); + free (*nc); + return (-1); + } + (void) strncpy((*nc)->ifname, names[i], + sizeof ((*nc)->ifname)); + (*nc)->naliases = num_aliases; + + res = smb_nic_get_IP((*nc)->ifname, &uip); + if (res == -1) { /* error retrieving IP address */ + (void) smb_nic_clear_name_list(names, numnics); + free ((*nc)->aliases); + free (*nc); + return (-1); + } + (*nc)->ip = uip; + if (smb_wins_is_excluded(uip, (ulong_t *)exclude, + nexclude)) + (*nc)->exclude = B_TRUE; + res = smb_nic_get_netmask((*nc)->ifname, &uip); + if (res == -1) { /* error retrieving netmask address */ + (void) smb_nic_clear_name_list(names, numnics); + free ((*nc)->aliases); + free (*nc); + return (-1); + } + (*nc)->mask = uip; + res = smb_nic_get_broadcast((*nc)->ifname, &uip); + if (res == -1) { /* error retrieving broadcast add */ + (void) smb_nic_clear_name_list(names, numnics); + free ((*nc)->aliases); + free (*nc); + return (-1); + } + (*nc)->broadcast = uip; + res = smb_nic_get_group((*nc)->ifname, + (*nc)->groupname); + if (res == -1) { /* error retrieving group name */ + (void) smb_nic_clear_name_list(names, numnics); + free ((*nc)->aliases); + free (*nc); + return (-1); + } + res = smb_nic_flags((*nc)->ifname, &flags); + if (res == -1) { /* error retrieving flags */ + (void) smb_nic_clear_name_list(names, numnics); + free ((*nc)->aliases); + free (*nc); + return (-1); + } + (*nc)->flags = flags; + /* + * If an interface has no associated alias, the alias + * field will be set to NULL + */ + res = smb_nic_get_IP_aliases((*nc)->ifname, &list); + if (res == -1) { + (*nc)->aliases[k] = NULL; + (void) smb_nic_clear_name_list(names, numnics); + free ((*nc)->aliases); + free (*nc); + return (-1); + } + + if (res == 0) { + (*nc)->aliases[k] = NULL; + + } else { /* There will be aliases */ + + (*nc)->aliases[0] = (char *)list->name; + while (list->next != NULL) { + (*nc)->aliases[j] = + (char *)(list->next); + j++; + list = list->next; + } + } + k++; + j = 1; + (*nc)++; /* increment pointer */ + } + } /* end for */ + + *nc = nc_array; + *number = k; + (void) smb_nic_clear_name_list(names, numnics); + return (0); +} + +/* + * Return a space separated list of interface names depending on specified + * flags. Either flags argument can be set to 0 if the caller chooses. + * Returns NULL if no interfaces match the passed flags + * flags_on: flags which must be on in each interface returned + * flags_off : flags which must be off in each interface returned + */ +char * +smb_nic_get_ifnames(int flags_on, int flags_off) +{ + struct ifconf ifc; + int numifs, i, sfd; + char *ifnames; + + + sfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sfd == -1) + return (NULL); + + if ((ioctl(sfd, SIOCGIFNUM, &numifs) == -1) || (numifs <= 0)) { + (void) close(sfd); + return (NULL); + } + + ifnames = malloc(numifs * (LIFNAMSIZ + 1)); + if (ifnames == NULL) { + return (NULL); + } + ifc.ifc_len = (numifs * sizeof (struct ifreq)); + ifc.ifc_req = malloc(numifs * sizeof (struct ifreq)); + if (ifc.ifc_req == NULL) { + free(ifnames); + return (NULL); + } + + if (ioctl(sfd, SIOCGIFCONF, &ifc) == -1) { + (void) close(sfd); + free(ifnames); + free(ifc.ifc_req); + return (NULL); + } + + for (i = 0; i < numifs; i++) { + if (ioctl(sfd, SIOCGIFFLAGS, &ifc.ifc_req[i]) == 0) { + if ((ifc.ifc_req[i].ifr_flags & + (flags_on | flags_off)) != flags_on) { + continue; + } + } + + (void) strcat(ifnames, ifc.ifc_req[i].ifr_name); + (void) strcat(ifnames, " "); + } + + if (strlen(ifnames) > 1) + ifnames[strlen(ifnames) - 1] = '\0'; + + (void) close(sfd); + free(ifc.ifc_req); + + return (ifnames); +} + +/* + * Function to determine if passed address is of form a.b.c.d. + */ +int +smb_nic_validate_ip_address(char *IP) +{ + in_addr_t addr; + if ((int)(addr = inet_addr(IP)) == -1) { + syslog(LOG_ERR, "%s", "IP-address must be" + " of the form a.b.c.d"); + return (addr); + } + else + return (0); + +} + +/* + * Get flags associated with if + * -1 means there was an error retrieving the data + * 0 success + */ +int +smb_nic_flags(char *interface, uint64_t *flag) +{ + struct lifreq lifrr; + int sfd; + + sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + + if (sfd < 0) { + syslog(LOG_ERR, "%s", "smb_get_nic_flags: socket open failed"); + return (-1); + } + + (void) strncpy(lifrr.lifr_name, interface, sizeof (lifrr.lifr_name)); + + if (ioctl(sfd, SIOCGLIFFLAGS, &lifrr) < 0) { + syslog(LOG_ERR, "%s", "smb_get_nic_flags: get flags failed"); + (void) close(sfd); + return (-1); + } + + (void) close(sfd); + if (flag != NULL) + *flag = lifrr.lifr_flags; + return (0); +} + +/* + * The following list is taken from if.h. The function takes the + * given interface name, and the passed flag(s), and returns true if + * the flag is associated with the interface, and false if not. + * + * IFF_UP interface is up + * IFF_BROADCAST broadcast address valid + * IFF_LOOPBACK is a loopback net + * IFF_POINTOPOINT interface is point-to-point link + * IFF_RUNNING resources allocated + * IFF_MULTICAST supports multicast + * IFF_MULTI_BCAST multicast using broadcast address + * IFF_UNNUMBERED non-unique address + * IFF_DHCPRUNNING DHCP controls this interface + * IFF_PRIVATE do not advertise + * IFF_DEPRECATED interface address deprecated + * IFF_ANYCAST Anycast address + * IFF_IPV4 IPv4 interface + * IFF_IPV6 IPv6 interface + * IFF_NOFAILOVER Don't failover on NIC failure + * IFF_FAILED NIC has failed + * IFF_STANDBY Standby NIC to be used on failures + * IFF_OFFLINE NIC has been offlined + * -1 means there was an error retrieving the data + * 0 indicates false - the flag isn't associated + * 1 indicates true - the flag is associated + */ +int +smb_nic_status(char *interface, uint64_t flag) +{ + struct lifreq lifrr; + int sfd; + + sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + + if (sfd < 0) { + syslog(LOG_ERR, "%s", "nic_status: socket open failed"); + return (-1); + } + + (void) strncpy(lifrr.lifr_name, interface, sizeof (lifrr.lifr_name)); + + if (ioctl(sfd, SIOCGLIFFLAGS, &lifrr) < 0) { + syslog(LOG_ERR, "%s", "nic_status: get flags failed"); + (void) close(sfd); + return (-1); + } + + if (lifrr.lifr_flags & flag) { + (void) close(sfd); + return (1); /* associated */ + } else { + (void) close(sfd); + return (0); /* not associated */ + } +} + +/* + * Free allocated memory for net_cfg structures. Takes number of allocated + * structures as argument also + */ +int +smb_nic_clear_niclist(net_cfg_t *niclist, int amount) +{ + int i, j = 0; + + if (niclist == NULL) + return (-1); + for (i = 0; i < amount; i++) { + while (niclist[i].aliases[j] != NULL) { + free(niclist[i].aliases[j]); + j++; + } + free(niclist[i].aliases); + j = 0; + } + free(niclist); + + return (0); +} + +int +smb_nic_free_niclist(net_cfg_list_t *niclist) +{ + return (smb_nic_clear_niclist(niclist->net_cfg_list, + niclist->net_cfg_cnt)); +} + +/* + * Free allocated memory for names lists. Takes number of allocated + * pointers as argument also + */ +int +smb_nic_clear_name_list(char **names, int amount) +{ + int i; + + for (i = 0; i < amount; i++) { + free(names[i]); + } + + free(names); + return (0); +} + +/* Free allocated memory for names lists. */ + +static void +smb_nic_clear_if_list(struct if_list *iflist) +{ + struct if_list *tmp; + + if (iflist == NULL) + return; + for (; iflist != NULL; iflist = tmp) { + tmp = iflist->next; + free(iflist); + } +} + +/* Free allocated memory for alias lists. */ +int +smb_nic_clear_ip_alias(struct ip_alias *iplist) +{ + struct ip_alias *tmp; + + for (; iplist != NULL; iplist = tmp) { + tmp = iplist->next; + free(iplist); + } + + return (0); +} + +/* + * smb_nic_lock + * + * Lock the nic table + */ +void +smb_nic_lock(void) +{ + (void) pthread_mutex_lock(&smb_nic_mutex); +} + +/* + * smb_nic_unlock + * + * Unlock the nic table. + * + * This function MUST be called after lock + */ +void +smb_nic_unlock(void) +{ + (void) pthread_mutex_unlock(&smb_nic_mutex); +} + +int +smb_nic_init() +{ + smb_nic_lock(); + smb_nic_list.net_cfg_cnt = 0; + (void) smb_nic_build_network_structures(&smb_nic_list.net_cfg_list, + &smb_nic_list.net_cfg_cnt); + smb_nic_unlock(); + return (0); +} + +/* + * Initialize interface list. + */ +void +smb_nic_build_info(void) +{ + smb_nic_lock(); + if (smb_nic_list.net_cfg_list) { + (void) smb_nic_free_niclist(&smb_nic_list); + smb_nic_list.net_cfg_list = NULL; + } + smb_nic_list.net_cfg_cnt = 0; + (void) smb_nic_build_network_structures(&smb_nic_list.net_cfg_list, + &smb_nic_list.net_cfg_cnt); + if (smb_nic_list.net_cfg_cnt == 0) { + syslog(LOG_ERR, "smb: No network interfaces are configured " + "smb server may not function properly"); + } + smb_nic_unlock(); +} + +/* + * Get number of interfaces. + */ +int +smb_nic_get_num(void) +{ + int sz; + smb_nic_lock(); + sz = smb_nic_list.net_cfg_cnt; + smb_nic_unlock(); + return (sz); +} + +/* + * Get if by index + * Returns: NULL if not found. + */ +net_cfg_t * +smb_nic_get_byind(int ind, net_cfg_t *cfg) +{ + if (cfg == NULL) + return (cfg); + smb_nic_lock(); + if (ind > smb_nic_list.net_cfg_cnt) { + smb_nic_unlock(); + return (NULL); + } + bcopy(&smb_nic_list.net_cfg_list[ind], cfg, sizeof (net_cfg_t)); + smb_nic_unlock(); + return (cfg); +} + +/* + * Get if by subnet + * Returns: NULL if not found. + */ +net_cfg_t * +smb_nic_get_bysubnet(uint32_t ipaddr, net_cfg_t *cfg) +{ + int i; + net_cfg_t *tcfg; + + if (cfg == NULL) + return (cfg); + smb_nic_lock(); + bzero(cfg, sizeof (net_cfg_t)); + for (i = 0; i < smb_nic_list.net_cfg_cnt; i++) { + tcfg = &smb_nic_list.net_cfg_list[i]; + if ((ipaddr & tcfg->mask) == + (tcfg->ip & tcfg->mask)) { + bcopy(tcfg, cfg, sizeof (net_cfg_t)); + smb_nic_unlock(); + return (cfg); + } + } + smb_nic_unlock(); + return (NULL); +} + +/* + * Get if by ip. + * Returns: NULL if not found. + */ +net_cfg_t * +smb_nic_get_byip(uint32_t ipaddr, net_cfg_t *cfg) +{ + int i; + net_cfg_t *tcfg; + + if (cfg == NULL) + return (cfg); + smb_nic_lock(); + bzero(cfg, sizeof (net_cfg_t)); + for (i = 0; i < smb_nic_list.net_cfg_cnt; i++) { + tcfg = &smb_nic_list.net_cfg_list[i]; + if (ipaddr == tcfg->ip) { + bcopy(tcfg, cfg, sizeof (net_cfg_t)); + smb_nic_unlock(); + return (cfg); + } + } + smb_nic_unlock(); + return (NULL); +} diff --git a/usr/src/lib/smbsrv/libsmbns/i386/Makefile b/usr/src/lib/smbsrv/libsmbns/i386/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/i386/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libsmbns/sparc/Makefile b/usr/src/lib/smbsrv/libsmbns/sparc/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/sparc/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libsmbns/sparcv9/Makefile b/usr/src/lib/smbsrv/libsmbns/sparcv9/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/sparcv9/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/smbsrv/libsmbrdr/Makefile b/usr/src/lib/smbsrv/libsmbrdr/Makefile new file mode 100644 index 0000000000..6b376227e4 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +HDRS= libsmbrdr.h + +include ../Makefile.smbsrv diff --git a/usr/src/lib/smbsrv/libsmbrdr/Makefile.com b/usr/src/lib/smbsrv/libsmbrdr/Makefile.com new file mode 100644 index 0000000000..822331f26b --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/Makefile.com @@ -0,0 +1,53 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +LIBRARY = libsmbrdr.a +VERS = .1 + +OBJS_COMMON = \ + smbrdr_ipc_util.o \ + smbrdr_lib.o \ + smbrdr_logon.o \ + smbrdr_netbios.o \ + smbrdr_netuse.o \ + smbrdr_read_andx.o \ + smbrdr_rpcpipe.o \ + smbrdr_session.o \ + smbrdr_transact.o + +OBJECTS= $(OBJS_COMMON) $(OBJS_SHARED) + +include ../../../Makefile.lib +include ../../Makefile.lib + +LDLIBS += -lsmb -lnsl -lsocket -lc + +SRCS= $(OBJS_COMMON:%.o=$(SRCDIR)/%.c) \ + $(OBJS_SHARED:%.o=$(SRC)/common/smbsrv/%.c) + +include ../../Makefile.targ +include ../../../Makefile.targ diff --git a/usr/src/lib/smbsrv/libsmbrdr/amd64/Makefile b/usr/src/lib/smbsrv/libsmbrdr/amd64/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/amd64/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/libsmbrdr.h b/usr/src/lib/smbsrv/libsmbrdr/common/libsmbrdr.h new file mode 100644 index 0000000000..846ac9d306 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/libsmbrdr.h @@ -0,0 +1,97 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBSMBRDR_H +#define _LIBSMBRDR_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <smbsrv/libsmb.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Redirector IPC functions + * + * The following functions are required by the mlsvc_validate_user to + * apply new authentication information for the authenticated IPC, rollback + * or commit the changes to the original authentication information. + */ +extern void smbrdr_ipc_set(char *, unsigned char *); +extern void smbrdr_ipc_commit(void); +extern void smbrdr_ipc_rollback(void); +extern int smbrdr_ipc_skip_lsa_query(void); +extern int smbrdr_ipc_get_mode(void); +extern void smbrdr_ipc_save_mode(char *val); +extern unsigned smbrdr_ipc_get_flags(void); +extern void smbrdr_ipc_set_fallback(void); +extern void smbrdr_ipc_unset_fallback(void); +extern int smbrdr_ipc_is_fallback(void); + +/* + * Functions for obtaining the resource domain administrator credentials. + */ +extern char *smbrdr_ipc_get_user(void); +extern char *smbrdr_ipc_get_passwd(void); +extern int smbrdr_ipc_is_valid(void); + + +/* Redirector LOGON functions */ +extern int mlsvc_anonymous_logon(char *, char *, char **); +extern int mlsvc_user_logon(char *, char *, char *, char *); +extern int mlsvc_admin_logon(char *, char *); + +extern int smbrdr_rpc_readx(int, char *, int); + + +/* Redirector rpcpipe functions */ +extern int mlsvc_open_pipe(char *, char *, char *, char *); +extern int mlsvc_close_pipe(int); + + +/* Redirector session functions */ +extern void smbrdr_init(void); +extern int mlsvc_locate_domain_controller(char *); +extern int mlsvc_session_native_values(int, int *, int *, int *); +extern void mlsvc_check_sessions(void); +extern int mlsvc_echo(char *); +extern void mlsvc_disconnect(char *); + + +extern int smbrdr_rpc_transact(int, char *, int, char *, int); + + +/* DEBUG functions */ +extern void smbrdr_dump_ofiles(void); +extern void smbrdr_dump_sessions(void); +extern void smbrdr_dump_netuse(); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSMBRDR_H */ diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/llib-lsmbrdr b/usr/src/lib/smbsrv/libsmbrdr/common/llib-lsmbrdr new file mode 100644 index 0000000000..d238202484 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/llib-lsmbrdr @@ -0,0 +1,31 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include <smbsrv/libsmbrdr.h> diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/mapfile-vers b/usr/src/lib/smbsrv/libsmbrdr/common/mapfile-vers new file mode 100644 index 0000000000..0ea435ee55 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/mapfile-vers @@ -0,0 +1,63 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +SUNWprivate { + global: + mlsvc_admin_logon; + mlsvc_anonymous_logon; + mlsvc_check_sessions; + mlsvc_close_pipe; + mlsvc_disconnect; + mlsvc_echo; + mlsvc_install_pdc_cb; + mlsvc_locate_domain_controller; + mlsvc_open_pipe; + mlsvc_session_native_values; + mlsvc_user_getauth; + mlsvc_user_logon; + smbrdr_dump_netuse; + smbrdr_dump_ofiles; + smbrdr_dump_sessions; + smbrdr_init; + smbrdr_ipc_get_mode; + smbrdr_ipc_commit; + smbrdr_ipc_get_flags; + smbrdr_ipc_get_user; + smbrdr_ipc_rollback; + smbrdr_ipc_set; + smbrdr_ipc_skip_lsa_query; + smbrdr_ipc_is_fallback; + smbrdr_ipc_is_valid; + smbrdr_ipc_get_passwd; + smbrdr_ipc_save_mode; + smbrdr_ipc_set_fallback; + smbrdr_ipc_unset_fallback; + smbrdr_rpc_readx; + smbrdr_rpc_transact; + local: + *; +}; diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr.h b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr.h new file mode 100644 index 0000000000..35db51adc6 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr.h @@ -0,0 +1,241 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SMBRDR_H_ +#define _SMBRDR_H_ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> +#include <synch.h> +#include <sys/types.h> + +#include <smbsrv/libsmbrdr.h> + +#include <smbsrv/cifs.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/smb.h> +#include <smbsrv/wintypes.h> + +#define SMBRDR_REQ_BUFSZ 4096 + +#define MAX_ACCOUNT_NAME 32 +#define MAX_SHARE_NAME 32 +#define MAX_SCOPE_NAME 64 +#define MAX_FILE_PATH 128 + +/* + * The number of shares and pipes is limited to 48 based on the note + * below. This really shouldn't cause a problem because we always + * our shares and named pipes are always opened and closed round every + * RPC transaction. This also tends to limit the number of active + * logons because we (currently) need two named pipes per logon. + * + * Q141709 Limit of 49 named pipe connections from a single workstation. + * If a named pipe server creates more than 49 distincly named pipes, a + * single client cannot connect more than 49 pipes on the named pipe + * server. Chapter 4, p113. Network Programming for Microsoft Windows + * Anthony Jones and Jim Ohlund, Microsoft Press, ISBN: 0-7356-0560-2 + */ +#define N_NETUSE_TABLE 48 +#define N_OFILE_TABLE 48 + +/* + * Logon's states + */ +#define SDB_LSTATE_START 0 +#define SDB_LSTATE_INIT 1 +#define SDB_LSTATE_LOGGING_OFF 2 +#define SDB_LSTATE_SETUP 3 + +#define SDB_LOGON_NONE 0 +#define SDB_LOGON_GUEST 1 +#define SDB_LOGON_ANONYMOUS 2 +#define SDB_LOGON_USER 3 + +typedef struct sdb_logon { + struct sdb_session *session; + char username[MAX_ACCOUNT_NAME]; + unsigned short uid; + unsigned int type; + unsigned short state; + smb_auth_info_t auth; +} sdb_logon_t; + +/* + * Session's states + * + * SDB_SSTATE_START ready to be used + * SDB_SSTATE_INIT initialized + * SDB_SSTATE_STALE lost transport connection + * SDB_SSTATE_DISCONNECTING disconnecting: logoff the user + * disconnect trees, close files + * SDB_SSTATE_CLEANING was in STALE state now just + * cleaning up + * SDB_SSTATE_CONNECTED got transport connection + * SDB_SSTATE_NEGOTIATED did SMB negotiate + */ +#define SDB_SSTATE_START 0 +#define SDB_SSTATE_INIT 1 +#define SDB_SSTATE_STALE 2 +#define SDB_SSTATE_DISCONNECTING 3 +#define SDB_SSTATE_CLEANING 4 +#define SDB_SSTATE_CONNECTED 5 +#define SDB_SSTATE_NEGOTIATED 6 + +#define SDB_SLCK_READ 1 +#define SDB_SLCK_WRITE 2 + +struct sdb_session { + smb_ntdomain_t di; + char scope[SMB_PI_MAX_SCOPE]; + char native_os[SMB_PI_MAX_NATIVE_OS]; + char native_lanman[SMB_PI_MAX_LANMAN]; + int sock; + short port; + unsigned short secmode; + uint32_t sesskey; + uint32_t challenge_len; + unsigned char challenge_key[32]; + unsigned char smb_flags; + unsigned short smb_flags2; + unsigned short vc; + uint32_t remote_caps; + unsigned short state; + unsigned int sid; /* session id */ + int remote_os; + int remote_lm; + int pdc_type; + smb_sign_ctx_t sign_ctx; + sdb_logon_t logon; + rwlock_t rwl; +}; + +/* + * Netuse's states + */ +#define SDB_NSTATE_START 0 +#define SDB_NSTATE_INIT 1 +#define SDB_NSTATE_DISCONNECTING 2 +#define SDB_NSTATE_CONNECTED 3 + +struct sdb_netuse { + struct sdb_session *session; + unsigned short state; + int letter; /* local identity */ + unsigned int sid; + unsigned short uid; + unsigned short tid; /* remote identity */ + char share[MAX_SHARE_NAME]; + mutex_t mtx; +}; + +/* + * Ofile's states + */ +#define SDB_FSTATE_START 0 +#define SDB_FSTATE_INIT 1 +#define SDB_FSTATE_CLOSING 2 +#define SDB_FSTATE_OPEN 3 + +struct sdb_ofile { + struct sdb_session *session; + struct sdb_netuse *netuse; + unsigned short state; + unsigned int sid; + unsigned short uid; + unsigned short tid; + unsigned short fid; /* remote identity */ + char path[MAX_FILE_PATH]; + mutex_t mtx; +}; + +typedef struct smbrdr_handle { + unsigned char *srh_buf; + smb_msgbuf_t srh_mbuf; + unsigned int srh_mbflags; + unsigned char srh_cmd; + struct sdb_session *srh_session; + struct sdb_logon *srh_user; + struct sdb_netuse *srh_tree; +} smbrdr_handle_t; + +/* + * smbrdr_netbios.c + */ +void nb_lock(void); +void nb_unlock(void); +void nb_close(int); +int nb_keep_alive(int); + +int nb_send(int, unsigned char *, unsigned); +int nb_rcv(int, unsigned char *, unsigned, long); +int nb_exchange(int, unsigned char *, unsigned, + unsigned char *, unsigned, long); +int nb_session_request(int, char *, char *, char *, char *); + +/* + * smbrdr_session.c + */ +int smbrdr_negotiate(char *); +struct sdb_session *smbrdr_session_lock(char *, char *, int); +void smbrdr_session_unlock(struct sdb_session *); + +/* + * smbrdr_logon.c + */ +int smbrdr_smb_logoff(struct sdb_logon *); + +/* smbrdr_netuse.c */ +void smbrdr_netuse_logoff(unsigned short); +struct sdb_netuse *smbrdr_netuse_get(int); +unsigned short mlsvc_tree_connect(char *, char *, char *); +int smbrdr_tree_disconnect(unsigned short); +void smbrdr_netuse_put(struct sdb_netuse *); + +/* + * smbrdr_rpcpipe.c + */ +void smbrdr_ofile_end_of_share(unsigned short); +struct sdb_ofile *smbrdr_ofile_get(int); +void smbrdr_ofile_put(struct sdb_ofile *); + +/* smbrdr_lib.c */ +DWORD smbrdr_request_init(smbrdr_handle_t *, unsigned char, + struct sdb_session *, struct sdb_logon *, struct sdb_netuse *); +DWORD smbrdr_send(smbrdr_handle_t *); +DWORD smbrdr_rcv(smbrdr_handle_t *, int); +DWORD smbrdr_exchange(smbrdr_handle_t *, smb_hdr_t *, long); +void smbrdr_handle_free(smbrdr_handle_t *); +int smbrdr_sign_init(struct sdb_session *, struct sdb_logon *); +int smbrdr_sign_fini(struct sdb_session *); +int smbrdr_sign_unset_key(struct sdb_session *); + +void smbrdr_lock_transport(void); +void smbrdr_unlock_transport(void); + +#endif /* _SMBRDR_H_ */ diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_ipc_util.c b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_ipc_util.c new file mode 100644 index 0000000000..908a439864 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_ipc_util.c @@ -0,0 +1,382 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * The IPC connection information is encapsulated within SMB Redirector. + * Utility functions are defined here to allow other modules to get and + * set the ipc configuration, as well as, to rollback or commit the + * changes to the original authentication information. + */ + +#include <string.h> +#include <strings.h> +#include <syslog.h> +#include <synch.h> + +#include <smbsrv/libsmbrdr.h> + +#include <smbsrv/mlsvc.h> +#include <smbsrv/smbinfo.h> +#include <smbrdr.h> +#include <smbrdr_ipc_util.h> + +/* + * The binary NTLM hash is 16 bytes. When it is converted to hexidecimal, + * it will be at most twice as long. + */ +#define SMBRDR_IPC_HEX_PASSWD_MAXLEN (SMBAUTH_HASH_SZ * 2) + 1 +#define SMBRDR_IPC_GETDOMAIN_TIMEOUT 10000 + +static rwlock_t smbrdr_ipc_lock; +static smbrdr_ipc_t ipc_info; +static smbrdr_ipc_t orig_ipc_info; + +/* + * smbrdr_ipc_init + * + * Get system configuration regarding IPC connection + * credentials and initialize related variables. + * This function will normally be called at startup + * (i.e. at the time smbrdr gets loaded). + */ +void +smbrdr_ipc_init(void) +{ + char *p; + + bzero(&ipc_info, sizeof (smbrdr_ipc_t)); + bzero(&orig_ipc_info, sizeof (smbrdr_ipc_t)); + + smb_config_rdlock(); + p = smb_config_getstr(SMB_CI_RDR_IPCMODE); + + if (!strncasecmp(p, IPC_MODE_AUTH, IPC_MODE_STRLEN)) { + ipc_info.mode = MLSVC_IPC_ADMIN; + + p = smb_config_getstr(SMB_CI_RDR_IPCUSER); + if (p) + (void) strlcpy(ipc_info.user, p, + MLSVC_ACCOUNT_NAME_MAX); + else + syslog(LOG_WARNING, "smbrdr: (ipc) no admin user name"); + + p = smb_config_get(SMB_CI_RDR_IPCPWD); + if (p) { + if (strlen(p) != SMBRDR_IPC_HEX_PASSWD_MAXLEN - 1) { + *ipc_info.passwd = 0; + syslog(LOG_WARNING, + "smbrdr: (ipc) invalid admin password"); + } else { + (void) hextobin(p, + SMBRDR_IPC_HEX_PASSWD_MAXLEN - 1, + ipc_info.passwd, SMBAUTH_HASH_SZ); + } + } else { + *ipc_info.passwd = 0; + syslog(LOG_WARNING, "smbrdr: (ipc) no admin password"); + } + + } else { + if (!strcasecmp(p, IPC_MODE_FALLBACK_ANON)) + ipc_info.flags |= IPC_FLG_FALLBACK_ANON; + + ipc_info.mode = MLSVC_IPC_ANON; + (void) strlcpy(ipc_info.user, MLSVC_ANON_USER, + MLSVC_ACCOUNT_NAME_MAX); + *ipc_info.passwd = 0; + } + smb_config_unlock(); +} + +/* + * smbrdr_ipc_set + * + * The given username and password hash will be applied to the + * ipc_info which will be used by mlsvc_validate_user(). + * + * If mlsvc_validate_user() succeeds, the calling function is responsible + * for invoking smbrdr_ipc_commit() for updating the environment + * variables. Otherwise, it should invoke smbrdr_ipc_rollback() to restore + * the previous credentials. + */ +void +smbrdr_ipc_set(char *plain_user, unsigned char *passwd_hash) +{ + (void) rw_wrlock(&smbrdr_ipc_lock); + if (ipc_info.flags & IPC_FLG_FALLBACK_ANON) + ipc_info.mode = MLSVC_IPC_ADMIN; + + (void) strlcpy(ipc_info.user, plain_user, sizeof (ipc_info.user)); + (void) memcpy(ipc_info.passwd, passwd_hash, SMBAUTH_HASH_SZ); + ipc_info.flags |= IPC_FLG_NEED_VERIFY; + (void) rw_unlock(&smbrdr_ipc_lock); + +} + +/* + * smbrdr_ipc_commit + * + * Save the new admin credentials as environment variables. + * The binary NTLM password hash is first converted to a + * hex string before storing in the environment variable. + * + * The credentials also saved to the original IPC info as + * rollback data in case the join domain process + * fails in the future. + */ +void +smbrdr_ipc_commit() +{ + unsigned char hexpass[SMBRDR_IPC_HEX_PASSWD_MAXLEN]; + + (void) rw_wrlock(&smbrdr_ipc_lock); + smb_config_wrlock(); + (void) smb_config_set(SMB_CI_RDR_IPCUSER, ipc_info.user); + (void) bintohex(ipc_info.passwd, sizeof (ipc_info.passwd), + (char *)hexpass, sizeof (hexpass)); + hexpass[SMBRDR_IPC_HEX_PASSWD_MAXLEN - 1] = 0; + (void) smb_config_set(SMB_CI_RDR_IPCPWD, (char *)hexpass); + + ipc_info.flags &= ~IPC_FLG_NEED_VERIFY; + + if (ipc_info.flags & IPC_FLG_FALLBACK_ANON) { + ipc_info.flags &= ~IPC_FLG_FALLBACK_ANON; + ipc_info.mode = MLSVC_IPC_ADMIN; + (void) smb_config_set(SMB_CI_RDR_IPCMODE, IPC_MODE_AUTH); + syslog(LOG_DEBUG, "smbrdr: (ipc) Authenticated IPC " + "connection has been restored"); + } + + (void) memcpy(&orig_ipc_info, &ipc_info, sizeof (smbrdr_ipc_t)); + smb_config_unlock(); + (void) rw_unlock(&smbrdr_ipc_lock); +} + +/* + * smbrdr_ipc_rollback + * + * Restore the original credentials + */ +void +smbrdr_ipc_rollback() +{ + (void) rw_wrlock(&smbrdr_ipc_lock); + (void) strlcpy(ipc_info.user, orig_ipc_info.user, + sizeof (ipc_info.user)); + (void) memcpy(ipc_info.passwd, orig_ipc_info.passwd, + sizeof (ipc_info.passwd)); + + ipc_info.flags &= ~IPC_FLG_NEED_VERIFY; + + if (ipc_info.flags & IPC_FLG_FALLBACK_ANON) + ipc_info.mode = MLSVC_IPC_ANON; + (void) rw_unlock(&smbrdr_ipc_lock); +} + +/* + * Get & Set functions + */ +int +smbrdr_ipc_get_mode() +{ + int mode; + + (void) rw_rdlock(&smbrdr_ipc_lock); + mode = ipc_info.mode; + (void) rw_unlock(&smbrdr_ipc_lock); + + return (mode); +} + +char * +smbrdr_ipc_get_user() +{ + char *user; + + (void) rw_rdlock(&smbrdr_ipc_lock); + user = ipc_info.user; + (void) rw_unlock(&smbrdr_ipc_lock); + return (user); +} + +char * +smbrdr_ipc_get_passwd() +{ + char *passwd; + + (void) rw_rdlock(&smbrdr_ipc_lock); + passwd = ipc_info.passwd; + (void) rw_unlock(&smbrdr_ipc_lock); + return (passwd); +} + +unsigned +smbrdr_ipc_get_flags() +{ + unsigned flags; + + (void) rw_rdlock(&smbrdr_ipc_lock); + flags = ipc_info.flags; + (void) rw_unlock(&smbrdr_ipc_lock); + return (flags); +} + +void +smbrdr_ipc_set_fallback() +{ + (void) rw_wrlock(&smbrdr_ipc_lock); + ipc_info.flags |= IPC_FLG_FALLBACK_ANON; + (void) rw_unlock(&smbrdr_ipc_lock); +} + +void +smbrdr_ipc_unset_fallback() +{ + (void) rw_wrlock(&smbrdr_ipc_lock); + ipc_info.flags &= ~IPC_FLG_FALLBACK_ANON; + (void) rw_unlock(&smbrdr_ipc_lock); +} + +/* + * Whether the smbrdr.ipc.mode is set to fallback,anon or not + */ +int +smbrdr_ipc_is_fallback() +{ + int is_fallback; + + smb_config_rdlock(); + is_fallback = (!strcasecmp(smb_config_getstr(SMB_CI_RDR_IPCMODE), + IPC_MODE_FALLBACK_ANON) ? 1 : 0); + smb_config_unlock(); + + return (is_fallback); +} + +/* + * smbrdr_ipc_save_mode + * + * Set the SMBRDR_IPC_MODE_ENV variable and update the + * IPC mode of the cache. + */ +void +smbrdr_ipc_save_mode(char *val) +{ + (void) rw_wrlock(&smbrdr_ipc_lock); + smb_config_wrlock(); + (void) smb_config_set(SMB_CI_RDR_IPCMODE, val); + ipc_info.mode = !strncasecmp(val, IPC_MODE_AUTH, IPC_MODE_STRLEN) + ? MLSVC_IPC_ADMIN : MLSVC_IPC_ANON; + smb_config_unlock(); + (void) rw_unlock(&smbrdr_ipc_lock); +} + +/* + * smbrdr_ipc_skip_lsa_query + * + * Determine whether LSA monitor should skip the LSA query due to the + * incomplete authentication information if IPC is configured to be + * authenticated. + */ +int +smbrdr_ipc_skip_lsa_query() +{ + char *user, *pwd; + + if (ipc_info.mode != MLSVC_IPC_ADMIN) + return (0); + + smb_config_rdlock(); + user = smb_config_get(SMB_CI_RDR_IPCUSER); + pwd = smb_config_get(SMB_CI_RDR_IPCPWD); + smb_config_unlock(); + if ((user == NULL) && pwd) + return (1); + + (void) rw_rdlock(&smbrdr_ipc_lock); + user = ipc_info.user; + pwd = ipc_info.passwd; + (void) rw_unlock(&smbrdr_ipc_lock); + + return (!(*user && *pwd)); +} + +static char * +smbrdr_ipc_modestr(int mode) +{ + switch (mode) { + case MLSVC_IPC_ANON: + return ("Anonymous"); + + case MLSVC_IPC_ADMIN: + return ("Authenticated"); + + default: + return ("Unknown"); + } +} + +/* + * For debugging purposes only. + */ +void +smbrdr_ipc_loginfo() +{ + smbrdr_ipc_t tmp; + smbrdr_ipc_t tmporg; + + (void) rw_rdlock(&smbrdr_ipc_lock); + (void) memcpy(&tmp, &ipc_info, sizeof (smbrdr_ipc_t)); + (void) memcpy(&tmporg, &orig_ipc_info, sizeof (smbrdr_ipc_t)); + (void) rw_unlock(&smbrdr_ipc_lock); + + syslog(LOG_DEBUG, "smbrdr: current IPC info:"); + syslog(LOG_DEBUG, "\t%s (user=%s, flags:0x%X)", + smbrdr_ipc_modestr(tmp.mode), tmp.user, tmp.flags); + + syslog(LOG_DEBUG, "smbrdr: original IPC info:"); + syslog(LOG_DEBUG, "\t%s (user=%s, flags:0x%X)", + smbrdr_ipc_modestr(tmporg.mode), tmporg.user, tmporg.flags); +} + +/* + * smbrdr_ipc_is_valid + * + * Determine whether the ipc_info has been validated or not. + * + */ +int +smbrdr_ipc_is_valid() +{ + int isvalid; + + (void) rw_rdlock(&smbrdr_ipc_lock); + isvalid = (ipc_info.flags & IPC_FLG_NEED_VERIFY) ? 0 : 1; + (void) rw_unlock(&smbrdr_ipc_lock); + + return (isvalid); +} diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_ipc_util.h b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_ipc_util.h new file mode 100644 index 0000000000..a967e91b86 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_ipc_util.h @@ -0,0 +1,85 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SMBSRV_IPC_UTIL_H +#define _SMBSRV_IPC_UTIL_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file defines the data structure for the IPC connection and utility + * function prototypes. + */ + +#include <smbsrv/mlsvc.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SMBRDR_IPC_MODE_ENV "smbrdr.ipc.mode" +#define SMBRDR_IPC_USER_ENV "smbrdr.ipc.user" +#define SMBRDR_IPC_PASSWD_ENV "smbrdr.ipc.passwd" + +#define IPC_MODE_STRLEN 4 +#define IPC_MODE_ANON "anon" +#define IPC_MODE_AUTH "auth" +#define IPC_MODE_FALLBACK_ANON "fallback,anon" + +#define IPC_FLG_FALLBACK_ANON 0x00000001 +#define IPC_FLG_NEED_VERIFY 0x00000002 + +/* + * smbrdr_ipc_t + * + * This structure contains information regarding the IPC configuration, + * as well as, the authentication info needed for connecting to the + * IPC$ share if the IPC connection is configured to be authenticated. + * + * IPC connection to the Primary Domain Controller [PDC] can be + * configured to be either anonymous or authenticated. Therefore, + * the IPC mode will be set to either one of the following values: + * MLSVC_IPC_ANON + * MLSVC_IPC_ADMIN + * + * The IPC_FLG_FALLBACK_ANON can be set in flags field to indicate whether + * a fallback from authenticated IPC to anonymous IPC has occurred. This + * flag will be unset once the join domain operation succeeds. + */ +typedef struct { + int mode; + char user[MLSVC_ACCOUNT_NAME_MAX]; + char passwd[SMBAUTH_HASH_SZ]; + unsigned flags; +} smbrdr_ipc_t; + + +void smbrdr_ipc_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _SMBSRV_IPC_UTIL_H */ diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_lib.c b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_lib.c new file mode 100644 index 0000000000..ac4743548c --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_lib.c @@ -0,0 +1,592 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file provides some common functionality for SMB Redirector + * module. + */ + +#include <unistd.h> +#include <string.h> +#include <strings.h> +#include <smbsrv/ntstatus.h> +#include <smbrdr.h> + +static DWORD smbrdr_handle_setup(smbrdr_handle_t *srh, unsigned char cmd, + struct sdb_session *session, struct sdb_logon *logon, + struct sdb_netuse *netuse); + +static int smbrdr_hdr_setup(smbrdr_handle_t *srh); + +static DWORD smbrdr_hdr_process(smbrdr_handle_t *srh, smb_hdr_t *smb_hdr); + +static int smbrdr_sign(smb_sign_ctx_t *sign_ctx, smb_msgbuf_t *mb); +static int smbrdr_sign_chk(smb_sign_ctx_t *sign_ctx, smb_msgbuf_t *mb, + unsigned char *signature); + +void smbrdr_lock_transport() { nb_lock(); } +void smbrdr_unlock_transport() { nb_unlock(); } + +/* + * smbrdr_request_init + * + * Setup a handle with given information and then + * setup a SMB header structure. + * + * Returns: + * + * NT_STATUS_NO_MEMORY no memory for creating request + * NT_STATUS_INTERNAL_ERROR header encode failed or crypto failed + * NT_STATUS_SUCCESS successful + */ +DWORD +smbrdr_request_init(smbrdr_handle_t *srh, + unsigned char cmd, + struct sdb_session *session, + struct sdb_logon *logon, + struct sdb_netuse *netuse) +{ + DWORD status; + + status = smbrdr_handle_setup(srh, cmd, session, logon, netuse); + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_DEBUG, "Smbrdr[%d]: initialization failed", cmd); + return (status); + } + + if (smbrdr_hdr_setup(srh) < SMB_HEADER_LEN) { + syslog(LOG_DEBUG, "Smbrdr[%d]: cannot setup header", cmd); + smbrdr_handle_free(srh); + return (NT_STATUS_INTERNAL_ERROR); + } + + return (NT_STATUS_SUCCESS); +} + +/* + * smbrdr_send + * + * Send the SMB packet pointed by the given handle over + * network. + * + * Returns: + * + * NT_STATUS_INTERNAL_ERROR crypto framework failure + * NT_STATUS_UNEXPECTED_NETWORK_ERROR send failed + * NT_STATUS_SUCCESS successful + */ +DWORD +smbrdr_send(smbrdr_handle_t *srh) +{ + int rc; + + if (smbrdr_sign(&srh->srh_session->sign_ctx, &srh->srh_mbuf) != + SMBAUTH_SUCCESS) + return (NT_STATUS_INTERNAL_ERROR); + + rc = nb_send(srh->srh_session->sock, srh->srh_buf, + smb_msgbuf_used(&srh->srh_mbuf)); + + if (rc < 0) { + /* + * Make the sequence number of the next SMB request even + * to avoid DC from failing the next SMB request with + * ACCESS_DENIED. + */ + smb_mac_dec_seqnum(&srh->srh_session->sign_ctx); + return (NT_STATUS_UNEXPECTED_NETWORK_ERROR); + } + + return (NT_STATUS_SUCCESS); +} + +/* + * smbrdr_rcv + * + * Receive a SMB response and decode the packet header. + * + * "Implementing CIFS" book, SMB requests always have an even sequence + * number and replies always have an odd. + * + * With the original code, if the SMB Redirector skip the counter increment + * in the event of any failure during SmbSessionSetupAndX, it causes the + * domain controller to fail the next SMB request(odd sequence number) + * with ACCESS_DENIED. + * + * Smbrdr module should use the same sequence number (i.e. ssc_seqnum of the + * SMB Sign context) for generating the MAC signature for all incoming + * responses per SmbTransact request. Otherwise, the validation will fail. + * It is now fixed by decrementing the sequence number prior to validating + * the subsequent responses for a single request. + * + * Returns: + * + * status code returned by smbrdr_hdr_process() + * NT_STATUS_UNEXPECTED_NETWORK_ERROR receive failed + * NT_STATUS_SUCCESS successful + */ +DWORD +smbrdr_rcv(smbrdr_handle_t *srh, int is_first_rsp) +{ + smb_hdr_t smb_hdr; + DWORD status; + int rc; + smb_sign_ctx_t *sign_ctx = &srh->srh_session->sign_ctx; + + rc = nb_rcv(srh->srh_session->sock, srh->srh_buf, SMBRDR_REQ_BUFSZ, 0); + if (rc < 0) { + smb_mac_inc_seqnum(sign_ctx); + return (NT_STATUS_UNEXPECTED_NETWORK_ERROR); + } + + /* initialize for processing response */ + smb_msgbuf_init(&srh->srh_mbuf, srh->srh_buf, rc, srh->srh_mbflags); + + status = smbrdr_hdr_process(srh, &smb_hdr); + if (status != NT_STATUS_SUCCESS) { + smb_mac_inc_seqnum(sign_ctx); + return (status); + } + + if (!is_first_rsp) + smb_mac_dec_seqnum(sign_ctx); + + if (!smbrdr_sign_chk(sign_ctx, + &srh->srh_mbuf, smb_hdr.extra.extra.security_sig)) { + syslog(LOG_ERR, "SmbrdrExchange[%d]: bad signature", + srh->srh_cmd); + return (NT_STATUS_INVALID_NETWORK_RESPONSE); + } + + return (NT_STATUS_SUCCESS); +} + +/* + * smbrdr_exchange + * + * Send the SMB packet pointed by the given handle over + * network. Receive the response and decode the packet header. + * + * From "Implementing CIFS" book, SMB requests always have an even sequence + * number and replies always have an odd. + * + * With the original code, if the SMB Redirector skips the counter increment + * in the event of any failure during SmbSessionSetupAndX, it causes the + * domain controller to fail the next SMB request(odd sequence number) + * with ACCESS_DENIED. + * + * Returns: + * + * status code returned by smbrdr_hdr_process() + * NT_STATUS_INTERNAL_ERROR crypto framework failure + * NT_STATUS_UNEXPECTED_NETWORK_ERROR send/receive failed + * NT_STATUS_SUCCESS successful + */ +DWORD +smbrdr_exchange(smbrdr_handle_t *srh, smb_hdr_t *smb_hdr, long timeout) +{ + smb_sign_ctx_t *sign_ctx; + smb_msgbuf_t *mb; + DWORD status; + int rc; + + mb = &srh->srh_mbuf; + sign_ctx = &srh->srh_session->sign_ctx; + + if (smbrdr_sign(sign_ctx, mb) != SMBAUTH_SUCCESS) + return (NT_STATUS_INTERNAL_ERROR); + + rc = nb_exchange(srh->srh_session->sock, + srh->srh_buf, smb_msgbuf_used(mb), + srh->srh_buf, SMBRDR_REQ_BUFSZ, timeout); + + if (rc < 0) { + syslog(LOG_ERR, "SmbrdrExchange[%d]: failed (%d)", + srh->srh_cmd, rc); + + if (srh->srh_cmd != SMB_COM_ECHO) { + /* + * Since SMB echo is used to check the session + * status then don't destroy the session if it's + * SMB echo. + */ + srh->srh_session->state = SDB_SSTATE_STALE; + } + smb_mac_inc_seqnum(sign_ctx); + return (NT_STATUS_UNEXPECTED_NETWORK_ERROR); + } + + /* initialize for processing response */ + smb_msgbuf_init(mb, srh->srh_buf, rc, srh->srh_mbflags); + + status = smbrdr_hdr_process(srh, smb_hdr); + if (status != NT_STATUS_SUCCESS) { + smb_mac_inc_seqnum(sign_ctx); + return (status); + } + + /* Signature validation */ + if (!smbrdr_sign_chk(sign_ctx, mb, smb_hdr->extra.extra.security_sig)) { + syslog(LOG_ERR, "SmbrdrExchange[%d]: bad signature", + srh->srh_cmd); + return (NT_STATUS_INVALID_NETWORK_RESPONSE); + } + + return (NT_STATUS_SUCCESS); +} + +/* + * smbrdr_handle_free + * + * Frees the memories allocated for the given handle. + */ +void +smbrdr_handle_free(smbrdr_handle_t *srh) +{ + if (srh) { + smb_msgbuf_term(&srh->srh_mbuf); + free(srh->srh_buf); + } +} + + +/* + * smbrdr_sign_init + * + * This function is called from SessionSetup and initialize the + * signing context for the session if the connected user isn't + * anonymous. This has to call before smbrdr_request_init() + * because it modifies smb_flags2. + * + * The following description is taken out from the "Implementing CIFS" + * book(pg. 304): + * + * "Once the MAC signing has been initialized within a session, all + * messages are numbered using the same counters and signed using + * the same Session Key. This is true even if additional SESSION + * SETUP ANDX exchanges occur." + * + * The original SMB packet signing implementation calculates a MAC + * key each time the SMB Redirector sends the SmbSessionSetupAndx + * request for any non-anonymous/non-guest user which is not desired + * whenever there is a change in the user session key. + * + * If NTLMv2 authentication is used, the MAC key generated for each + * SessionSetup is unique. Since the domain controller expects the + * signature of all incoming requests are signed by the same MAC key + * (i.e. the one that generated for the first non-anonymous SessionSetup), + * access denied is returned for any subsequent SmbSessionSetupAndX + * request. + */ +int +smbrdr_sign_init(struct sdb_session *session, struct sdb_logon *logon) +{ + smb_sign_ctx_t *sign_ctx; + int rc = 0; + + sign_ctx = &session->sign_ctx; + + if ((sign_ctx->ssc_flags & SMB_SCF_REQUIRED) && + !(sign_ctx->ssc_flags & SMB_SCF_STARTED) && + (logon->type != SDB_LOGON_ANONYMOUS)) { + if (smb_mac_init(sign_ctx, &logon->auth) != SMBAUTH_SUCCESS) { + syslog(LOG_DEBUG, "SmbrdrSignInit: mac_init failed"); + return (-1); + } + sign_ctx->ssc_flags |= + (SMB_SCF_STARTED | SMB_SCF_KEY_ISSET_THIS_LOGON); + session->smb_flags2 |= SMB_FLAGS2_SMB_SECURITY_SIGNATURE; + syslog(LOG_DEBUG, "SmbrdrSignInit: mac key is set"); + rc = 1; + } + + return (rc); +} + +/* + * smbrdr_sign_fini + * + * Invalidate the MAC key if the first non-anonymous/non-guest user logon + * fail. + */ +int +smbrdr_sign_fini(struct sdb_session *session) +{ + smb_sign_ctx_t *sign_ctx; + int rc = 0; + + sign_ctx = &session->sign_ctx; + + if (sign_ctx->ssc_flags & SMB_SCF_KEY_ISSET_THIS_LOGON) { + sign_ctx->ssc_flags &= ~SMB_SCF_STARTED; + sign_ctx->ssc_flags &= ~SMB_SCF_KEY_ISSET_THIS_LOGON; + sign_ctx->ssc_seqnum = 0; + syslog(LOG_DEBUG, "SmbrdrSignFini: packet signing stopped"); + rc = 1; + } + + return (rc); +} + +/* + * smbrdr_sign_unset_key + * + * The SMB_SCF_KEY_ISSET_THIS_LOGON should be unset upon the successful + * SmbSessionSetupAndX request for the first non-anonymous/non-guest + * logon. + */ +int +smbrdr_sign_unset_key(struct sdb_session *session) +{ + smb_sign_ctx_t *sign_ctx; + int rc = 0; + + sign_ctx = &session->sign_ctx; + + if (sign_ctx->ssc_flags & SMB_SCF_KEY_ISSET_THIS_LOGON) { + sign_ctx->ssc_flags &= ~SMB_SCF_KEY_ISSET_THIS_LOGON; + syslog(LOG_DEBUG, "SmbrdrSignUnsetKey: unset KEY_ISSET flag"); + rc = 1; + } + + return (rc); +} + +/* + * smbrdr_handle_setup + * + * Allocates a buffer for sending/receiving a SMB request. + * Initialize a smb_msgbuf structure with the allocated buffer. + * Setup given handle (srh) with the specified information. + * + * Returns: + * + * NT_STATUS_NO_MEMORY not enough memory + * NT_STATUS_SUCCESS successful + */ +static DWORD +smbrdr_handle_setup(smbrdr_handle_t *srh, + unsigned char cmd, + struct sdb_session *session, + struct sdb_logon *logon, + struct sdb_netuse *netuse) +{ + srh->srh_buf = (unsigned char *)malloc(SMBRDR_REQ_BUFSZ); + if (srh->srh_buf == 0) { + syslog(LOG_ERR, "Smbrdr[%d]: resource shortage", cmd); + return (NT_STATUS_NO_MEMORY); + } + bzero(srh->srh_buf, SMBRDR_REQ_BUFSZ); + + srh->srh_mbflags = (session->remote_caps & CAP_UNICODE) + ? SMB_MSGBUF_UNICODE : 0; + + smb_msgbuf_init(&srh->srh_mbuf, srh->srh_buf, + SMBRDR_REQ_BUFSZ, srh->srh_mbflags); + + srh->srh_cmd = cmd; + srh->srh_session = session; + srh->srh_user = logon; + srh->srh_tree = netuse; + + return (NT_STATUS_SUCCESS); +} + +/* + * smbrdr_hdr_setup + * + * Build an SMB header based on the information in the given handle. + * The SMB header is described in section 3.2 of the CIFS spec. + * As this is a canned function, no error checking is performed here. + * The return value from smb_msgbuf_encode is simply returned to the caller. + */ +static int +smbrdr_hdr_setup(smbrdr_handle_t *srh) +{ + static unsigned short my_pid = 0; + + if (!my_pid) + my_pid = getpid(); + + return (smb_msgbuf_encode(&srh->srh_mbuf, "Mb4.bw12.wwww", + srh->srh_cmd, + srh->srh_session->smb_flags, + srh->srh_session->smb_flags2, + (srh->srh_tree) ? srh->srh_tree->tid : 0, + my_pid, + (srh->srh_user) ? srh->srh_user->uid : 0, + 0 /* mid */)); +} + +/* + * Canned SMB header decode. + */ +static int +smb_decode_nt_hdr(smb_msgbuf_t *mb, smb_hdr_t *hdr) +{ + return (smb_msgbuf_decode(mb, SMB_HEADER_NT_FMT, + &hdr->command, + &hdr->status.ntstatus, + &hdr->flags, + &hdr->flags2, + &hdr->pid_high, + SMB_SIG_SIZE, + &hdr->extra.extra.security_sig, + &hdr->tid, + &hdr->pid, + &hdr->uid, + &hdr->mid)); +} + +/* + * smbrdr_hdr_process + * + * Assuming 'srh->srh_mbuf' contains a response from a Windows client, + * decodes the 32 bytes SMB header. + * + * Returns: + * + * NT_STATUS_INVALID_NETWORK_RESPONSE error decoding the header + * NT_STATUS_REPLY_MESSAGE_MISMATCH response doesn't match the request + * NT_STATUS_SUCCESS successful + * smb_hdr->status.ntstatus error returned by server + */ +static DWORD +smbrdr_hdr_process(smbrdr_handle_t *srh, smb_hdr_t *smb_hdr) +{ + int rc; + + /* + * Returns the number of decoded bytes on success + * or some negative MSGBUF_XXX error code on failure + */ + rc = smb_decode_nt_hdr(&srh->srh_mbuf, smb_hdr); + + if (rc < SMB_HEADER_LEN) { + syslog(LOG_ERR, "Smbrdr[%d]: bad SMB header (%d)", + srh->srh_cmd, rc); + return (NT_STATUS_INVALID_NETWORK_RESPONSE); + } + + if (smb_hdr->status.ntstatus != 0) { + /* + * MSDN article: 193839 + * "The buffer overflow status is usually returned by a Windows + * server to inform the client that there is more data in its + * buffer than it could put in the packet. + * + * The buffer overflow should not be considered as an error. + * Subsequent SmbReadX request is required to obtain the + * remaining data in the server's buffer. + */ + if (NT_SC_VALUE(smb_hdr->status.ntstatus) + == NT_STATUS_BUFFER_OVERFLOW) { + syslog(LOG_DEBUG, "Smbrdr[%d]: %s", srh->srh_cmd, + xlate_nt_status(smb_hdr->status.ntstatus)); + } else { + syslog(LOG_ERR, "Smbrdr[%d]: request failed (%s)", + srh->srh_cmd, + xlate_nt_status(smb_hdr->status.ntstatus)); + return (smb_hdr->status.ntstatus); + } + } + + if (smb_hdr->command != srh->srh_cmd) { + syslog(LOG_ERR, "Smbrdr[%d]: reply mismatch (%d)", + srh->srh_cmd, smb_hdr->command); + return (NT_STATUS_REPLY_MESSAGE_MISMATCH); + } + + return (NT_STATUS_SUCCESS); +} + +/* + * smbrdr_sign + * + * Signs the given outgoing packet according to the + * specified signing context. + * + * The client and server each maintain an integer counter + * which they initialize to zero. Both counters are + * incremented for every SMB message - that's once for a + * request and once for a reply. As a result, requests sent + * by SMB Redirector always have an even sequence number + * and replies from the Windows server always have an odd + * number. + * + * Based on the observed Windows 2003 behavior, any SMB + * request will fail with NT_STATUS_ACCESS_DENIED if its + * sequence number is not even. + * + * The function can fail if there is trouble with the cryptographic + * framework and if that happens SMBAUTH_FAILURE is returned. In the + * normal case SMBAUTH_SUCCESS is returned. + */ +static int +smbrdr_sign(smb_sign_ctx_t *sign_ctx, smb_msgbuf_t *mb) +{ + if (sign_ctx->ssc_flags & SMB_SCF_STARTED) { + if (sign_ctx->ssc_seqnum % 2) { + syslog(LOG_DEBUG, "SmbrdrSign: even sequence number" + " is expected(%d)", + sign_ctx->ssc_seqnum); + } + if (smb_mac_sign(sign_ctx, smb_msgbuf_base(mb), + smb_msgbuf_used(mb)) != SMBAUTH_SUCCESS) + return (SMBAUTH_FAILURE); + sign_ctx->ssc_seqnum++; + } + return (SMBAUTH_SUCCESS); +} + + +/* + * smbrdr_sign_chk + * + * Validates SMB MAC signature in the in-coming message. + * Return 1 if the signature are match; otherwise, return 0; + * + * When packet signing is enabled, the sequence number kept in the + * sign_ctx structure will be incremented when a SMB request is + * sent and upon the receipt of the first SmbTransact response + * if SMB fragmentation occurs. + */ +static int +smbrdr_sign_chk(smb_sign_ctx_t *sign_ctx, smb_msgbuf_t *mb, + unsigned char *signature) +{ + int sign_ok = 1; + + if (sign_ctx->ssc_flags & SMB_SCF_STARTED) { + (void) memcpy(sign_ctx->ssc_sign, signature, SMB_SIG_SIZE); + sign_ok = smb_mac_chk(sign_ctx, smb_msgbuf_base(mb), + smb_msgbuf_size(mb)); + sign_ctx->ssc_seqnum++; + } + + return (sign_ok); +} diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_logon.c b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_logon.c new file mode 100644 index 0000000000..7dee7e41d2 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_logon.c @@ -0,0 +1,710 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * SMB session logon and logoff functions. See CIFS section 4.1. + */ + +#include <pthread.h> +#include <string.h> +#include <strings.h> +#include <syslog.h> +#include <synch.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <smbsrv/libsmbrdr.h> + +#include <smbsrv/ntstatus.h> +#include <smbsrv/smb.h> +#include <smbrdr_ipc_util.h> +#include <smbrdr.h> + +#define SMBRDR_PWD_NULL 0 +#define SMBRDR_PWD_USER 1 +#define SMBRDR_PWD_HASH 2 + +static int smbrdr_smb_session_setupandx(struct sdb_logon *logon); +static boolean_t smbrdr_logon_validate(char *server, char *username); +static struct sdb_logon *smbrdr_logon_init(struct sdb_session *session, + char *username, char *pwd, int pwd_type); +static int smbrdr_logon_user(char *server, char *username, char *pwd, + int pwd_type); +static int smbrdr_authenticate(char *primary_domain, char *account_name, + char *pwd, int pwd_type); + +/* + * mlsvc_anonymous_logon + * + * Set up an anonymous session. If the session to the resource domain + * controller appears to be okay we shouldn't need to do anything here. + * Otherwise we clean up the stale session and create a new one. + */ +int +mlsvc_anonymous_logon(char *domain_controller, char *domain_name, + char **username) +{ + int rc = 0; + + if (username == NULL) { + syslog(LOG_ERR, "smbrdr: (anon logon) %s", + xlate_nt_status(NT_STATUS_INVALID_PARAMETER)); + return (-1); + } + + /* + * if the system is configured to establish Authenticated IPC + * connection to PDC + */ + if (smbrdr_ipc_get_mode() == MLSVC_IPC_ADMIN) { + rc = mlsvc_admin_logon(domain_controller, domain_name); + /* + * it is possible for the system to fallback to use + * anonymous IPC + */ + if (smbrdr_ipc_get_mode() != MLSVC_IPC_ADMIN) + *username = MLSVC_ANON_USER; + else + *username = smbrdr_ipc_get_user(); + + syslog(LOG_DEBUG, "smbrdr: (admin logon) %s", *username); + return (rc); + } + + *username = MLSVC_ANON_USER; + + if (smbrdr_logon_validate(domain_controller, MLSVC_ANON_USER)) + /* session & user are good use them */ + return (0); + + + if (smbrdr_negotiate(domain_name) != 0) { + syslog(LOG_ERR, "smbrdr: (anon logon) negotiate <%s> failed", + (domain_name ? domain_name : "NoName")); + return (-1); + } + + if (smbrdr_logon_user(domain_controller, MLSVC_ANON_USER, 0, + SMBRDR_PWD_NULL) < 0) { + syslog(LOG_ERR, "smbrdr: (anon logon) logon failed"); + rc = -1; + } + + return (rc); +} + +int +mlsvc_user_getauth(char *domain_controller, char *username, + smb_auth_info_t *auth) +{ + struct sdb_session *session; + + if (auth) { + bzero(auth, sizeof (smb_auth_info_t)); + session = smbrdr_session_lock(domain_controller, username, + SDB_SLCK_READ); + if (session) { + *auth = session->logon.auth; + smbrdr_session_unlock(session); + return (0); + } + } + + return (-1); +} + +/* + * mlsvc_user_logon + * + * Set up a user session. If the session to the resource domain controller + * appears to be okay we shouldn't need to do anything here. Otherwise we + * clean up the stale session and create a new one. Once a session is + * established, we leave it intact. It should only need to be set up again + * due to an inactivity timeout or a domain controller reset. + */ +int +mlsvc_user_logon(char *domain_controller, char *domain_name, char *username, + char *password) +{ + int erc; + + if (smbrdr_logon_validate(domain_controller, username)) + return (0); + + if (smbrdr_negotiate(domain_name) != 0) { + syslog(LOG_ERR, "smbrdr: (user logon) negotiate failed"); + return (-1); + } + + erc = smbrdr_authenticate(domain_name, username, password, + SMBRDR_PWD_USER); + + return ((erc == AUTH_USER_GRANT) ? 0 : -1); +} + +/* + * mlsvc_admin_logon + * + * Unlike mlsvc_user_logon, mlsvc_admin_logon doesn't take + * any username or password as function arguments. + */ +int +mlsvc_admin_logon(char *domain_controller, char *domain_name) +{ + char password[PASS_LEN + 1]; + int erc; + char *username, *dummy; + + username = smbrdr_ipc_get_user(); + (void) memcpy(password, smbrdr_ipc_get_passwd(), SMBAUTH_HASH_SZ); + + if (*username == 0) { + syslog(LOG_ERR, "smbrdr: admin logon (no admin user)"); + return (-1); + } + + if (smbrdr_logon_validate(domain_controller, username)) + return (0); + + if (smbrdr_negotiate(domain_name) != 0) { + syslog(LOG_ERR, "smbrdr: admin logon (negotiate failed)"); + return (-1); + } + + erc = smbrdr_authenticate(domain_name, username, password, + SMBRDR_PWD_HASH); + + /* + * Fallback to anonmyous IPC logon if the IPC password hash is no + * longer valid. It happens when the administrator password has + * been reset from the Domain Controller. + */ + if (erc < 0 && smbrdr_ipc_is_valid()) { + if (!smbrdr_ipc_is_fallback()) + syslog(LOG_DEBUG, "smbrdr: admin logon " + "(fallback to anonymous IPC)"); + smbrdr_ipc_set_fallback(); + smbrdr_ipc_save_mode(IPC_MODE_FALLBACK_ANON); + erc = mlsvc_anonymous_logon(domain_controller, domain_name, + &dummy); + } + + return ((erc == AUTH_USER_GRANT) ? 0 : -1); +} + +/* + * smbrdr_authenticate + * + * Authenticate primary_domain\account_name. + * + * Returns: + * 0 User access granted + * 1 Guest access granted + * 2 IPC access granted + * (<0) Error + */ +static int +smbrdr_authenticate(char *primary_domain, char *account_name, + char *pwd, int pwd_type) +{ + smb_ntdomain_t *di; + + if (pwd == NULL) + return (AUTH_USER_GRANT | AUTH_IPC_ONLY_GRANT); + + + if ((di = smb_getdomaininfo(0)) == 0) { + syslog(LOG_ERR, "MlsvcAuthenticate[%s]: %s", account_name, + xlate_nt_status(NT_STATUS_CANT_ACCESS_DOMAIN_INFO)); + return (-1); + } + + /* + * Ensure that the domain name is uppercase. + */ + (void) utf8_strupr(primary_domain); + + /* + * We can only authenticate a user via a controller in the user's + * primary domain. If the user's domain name doesn't match the + * authenticating server's domain, reject the request before we + * create a logon entry for the user. Although the logon will be + * denied eventually, we don't want a logon structure for a user + * in the resource domain that is pointing to a session structure + * for the account domain. If this happened to be our resource + * domain user, we would not be able to use that account to connect + * to the resource domain. + */ + if (strcasecmp(di->domain, primary_domain)) { + syslog(LOG_ERR, "MlsvcAuthenticate: %s\\%s: not account domain", + primary_domain, account_name); + return (-2); + } + + return (smbrdr_logon_user(di->server, account_name, pwd, pwd_type)); +} + +/* + * smbrdr_logon_user + * + * This is the entry point for logging a user onto the domain. The + * session structure should have been obtained via a successful call + * to smbrdr_smb_connect. We allocate a logon structure to hold the + * user details and attempt to logon using smbrdr_smb_session_setupandx. Note + * that we expect the password fields to have been encrypted before + * this call. + * + * On success, the logon structure will be returned. Otherwise a null + * pointer will be returned. + */ +static int +smbrdr_logon_user(char *server, char *username, char *pwd, int pwd_type) +{ + struct sdb_session *session; + struct sdb_logon *logon; + struct sdb_logon old_logon; + + if (server == 0 || username == 0 || + ((pwd == 0) && (pwd_type != SMBRDR_PWD_NULL))) { + return (-1); + } + + session = smbrdr_session_lock(server, 0, SDB_SLCK_WRITE); + if (session == 0) { + syslog(LOG_ERR, "smbrdr: (logon[%s]) no session with %s", + username, server); + return (-1); + } + + bzero(&old_logon, sizeof (struct sdb_logon)); + + logon = &session->logon; + if (logon->type != SDB_LOGON_NONE) { + if (strcasecmp(logon->username, username) == 0) { + /* The requested user has already been logged in */ + smbrdr_session_unlock(session); + return ((logon->type == SDB_LOGON_GUEST) + ? AUTH_GUEST_GRANT : AUTH_USER_GRANT); + } + + old_logon = *logon; + } + + logon = smbrdr_logon_init(session, username, pwd, pwd_type); + + if (logon == 0) { + syslog(LOG_ERR, "smbrdr: (logon[%s]) resource shortage", + username); + smbrdr_session_unlock(session); + return (-1); + } + + if (smbrdr_smb_session_setupandx(logon) < 0) { + free(logon); + smbrdr_session_unlock(session); + return (-1); + } + + session->logon = *logon; + free(logon); + + if (old_logon.type != SDB_LOGON_NONE) { + (void) smbrdr_smb_logoff(&old_logon); + } + + smbrdr_session_unlock(session); + return ((logon->type == SDB_LOGON_GUEST) + ? AUTH_GUEST_GRANT : AUTH_USER_GRANT); +} + + +/* + * smbrdr_smb_session_setupandx + * + * Build and send an SMB session setup command. This is used to log a + * user onto the domain. See CIFS section 4.1.2. + * + * Returns 0 on success. Otherwise returns a -ve error code. + */ +static int +smbrdr_smb_session_setupandx(struct sdb_logon *logon) +{ + struct sdb_session *session; + smb_hdr_t smb_hdr; + smbrdr_handle_t srh; + smb_msgbuf_t *mb; + char *native_os; + char *native_lanman; + unsigned short data_bytes; + unsigned short guest; + unsigned long capabilities; + unsigned short null_size; + size_t (*strlen_fn)(const char *s); + DWORD status; + int rc; + + /* + * Paranoia check - we should never get this + * far without a valid session structure. + */ + if ((session = logon->session) == 0) { + syslog(LOG_ERR, "smbrdr_smb_session_setupandx: no data"); + return (-1); + } + + if (session->remote_caps & CAP_UNICODE) { + strlen_fn = mts_wcequiv_strlen; + null_size = sizeof (mts_wchar_t); + session->smb_flags2 |= SMB_FLAGS2_UNICODE; + } else { + strlen_fn = strlen; + null_size = sizeof (char); + } + + if (smbrdr_sign_init(session, logon) < 0) + return (-1); + + status = smbrdr_request_init(&srh, SMB_COM_SESSION_SETUP_ANDX, + session, 0, 0); + + if (status != NT_STATUS_SUCCESS) { + (void) smbrdr_sign_fini(session); + syslog(LOG_ERR, "SmbrdrSessionSetup: %s", + xlate_nt_status(status)); + return (-1); + } + mb = &srh.srh_mbuf; + + /* + * Regardless of the server's capabilities or what's + * reported in smb_flags2, we should report our full + * capabilities. + */ + capabilities = CAP_UNICODE | CAP_NT_SMBS | CAP_STATUS32; + + /* + * Compute the BCC for unicode or ASCII strings. + */ + data_bytes = logon->auth.ci_len + logon->auth.cs_len + null_size; + data_bytes += strlen_fn(session->native_os) + null_size; + data_bytes += strlen_fn(session->native_lanman) + null_size; + + if (logon->type == SDB_LOGON_ANONYMOUS) { + /* + * Anonymous logon: no username or domain name. + * We still need to include two null characters. + */ + data_bytes += (2 * null_size); + + rc = smb_msgbuf_encode(mb, "bb.wwwwlwwllwlu.u.", + 13, /* smb_wct */ + 0xff, /* AndXCommand (none) */ + 32 + 26 + 3 + data_bytes, /* AndXOffset */ + SMBRDR_REQ_BUFSZ, /* MaxBufferSize */ + 1, /* MaxMpxCount */ + 0, /* VcNumber */ + 0, /* SessionKey */ + 1, /* CaseInsensitivePassLength */ + 0, /* CaseSensitivePassLength */ + 0, /* Reserved */ + capabilities, /* Capabilities */ + data_bytes, /* smb_bcc */ + 0, /* No user or domain */ + session->native_os, /* NativeOS */ + session->native_lanman); /* NativeLanMan */ + } else { + data_bytes += strlen_fn(logon->username) + null_size; + data_bytes += strlen_fn(session->di.domain) + null_size; + + rc = smb_msgbuf_encode(mb, "bb.wwwwlwwllw#c#cuuu.u.", + 13, /* smb_wct */ + 0xff, /* AndXCommand (none) */ + 32 + 26 + 3 + data_bytes, /* AndXOffset */ + SMBRDR_REQ_BUFSZ, /* MaxBufferSize */ + 1, /* MaxMpxCount */ + session->vc, /* VcNumber */ + session->sesskey, /* SessionKey */ + logon->auth.ci_len, /* CaseInsensitivePassLength */ + logon->auth.cs_len, /* CaseSensitivePassLength */ + 0, /* Reserved */ + capabilities, /* Capabilities */ + data_bytes, /* smb_bcc */ + logon->auth.ci_len, /* ci length spec */ + logon->auth.ci, /* CaseInsensitivePassword */ + logon->auth.cs_len, /* cs length spec */ + logon->auth.cs, /* CaseSensitivePassword */ + logon->username, /* AccountName */ + session->di.domain, /* PrimaryDomain */ + session->native_os, /* NativeOS */ + session->native_lanman); /* NativeLanMan */ + } + + if (rc <= 0) { + syslog(LOG_ERR, "smbrdr_smb_session_setupandx: encode failed"); + smbrdr_handle_free(&srh); + (void) smbrdr_sign_fini(session); + return (-1); + } + + status = smbrdr_exchange(&srh, &smb_hdr, 0); + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrSessionSetup: %s", + xlate_nt_status(status)); + smbrdr_handle_free(&srh); + (void) smbrdr_sign_fini(session); + return (-1); + } + + rc = smb_msgbuf_decode(mb, "5.w2.u", &guest, &native_os); + + /* + * There was a problem in decoding response from + * a Samba 2.x PDC. This server sends strings in ASCII + * format and there is one byte with value 0 between + * native_os and native_lm: + * + * FF 53 4D 42 73 00 .SMBs. + * 00 00 00 88 01 00 00 00 00 00 00 00 00 00 00 00 ................ + * 00 00 00 00 BB 00 64 00 00 00 03 FF 00 00 00 01 ......d......... + * 00 1C 00 55 6E 69 78 00 53 61 6D 62 61 20 32 2E ...Unix.Samba.2. + * 32 2E 38 61 00 53 41 4D 42 41 5F 44 4F 4D 00 2.8a.SAMBA_DOM. + * + * The byte doesn't seem to be padding because when change in + * native OS from Unix to Unix1 the 0 byte is still there: + * + * FF 53 4D 42 73 00 .SMBs. + * 00 00 00 88 01 00 00 00 00 00 00 00 00 00 00 00 ................ + * 00 00 00 00 BB 00 64 00 00 00 03 FF 00 00 00 00 ......d......... + * 00 1D 00 55 6E 69 78 31 00 53 61 6D 62 61 20 32 ...Unix1.Samba.2 + * 2E 32 2E 38 61 00 53 41 4D 42 41 5F 44 4F 4D 00 .2.8a.SAMBA_DOM. + */ + if (rc > 0) { + if (session->remote_caps & CAP_UNICODE) + rc = smb_msgbuf_decode(mb, "u", &native_lanman); + else + rc = smb_msgbuf_decode(mb, ".u", &native_lanman); + } + + if (rc <= 0) { + syslog(LOG_ERR, "RdrSessionSetup: decode failed"); + smbrdr_handle_free(&srh); + (void) smbrdr_sign_fini(session); + return (-1); + } + + session->remote_os = smbnative_os_value(native_os); + session->remote_lm = smbnative_lm_value(native_lanman); + session->pdc_type = smbnative_pdc_value(native_lanman); + + logon->uid = smb_hdr.uid; + if (guest) + logon->type = SDB_LOGON_GUEST; + + smbrdr_handle_free(&srh); + (void) smbrdr_sign_unset_key(session); + + logon->state = SDB_LSTATE_SETUP; + + return (0); +} + +/* + * smbrdr_smb_logoff + * + * Build and send an SMB session logoff (SMB_COM_LOGOFF_ANDX) command. + * This is the inverse of an SMB_COM_SESSION_SETUP_ANDX. See CIFS + * section 4.1.3. The logon structure should have been obtained from a + * successful call to smbrdr_logon_user. + * + * Returns 0 on success. Otherwise returns a -ve error code. + */ +int +smbrdr_smb_logoff(struct sdb_logon *logon) +{ + struct sdb_session *session; + smbrdr_handle_t srh; + smb_hdr_t smb_hdr; + DWORD status; + int rc; + + if (logon->state != SDB_LSTATE_SETUP) { + /* No user to logoff */ + bzero(logon, sizeof (struct sdb_logon)); + return (0); + } + + if ((session = logon->session) == 0) { + bzero(logon, sizeof (struct sdb_logon)); + return (0); + } + + logon->state = SDB_LSTATE_LOGGING_OFF; + smbrdr_netuse_logoff(logon->uid); + + if ((session->state != SDB_SSTATE_NEGOTIATED) && + (session->state != SDB_SSTATE_DISCONNECTING)) { + bzero(logon, sizeof (struct sdb_logon)); + return (0); + } + + status = smbrdr_request_init(&srh, SMB_COM_LOGOFF_ANDX, + session, logon, 0); + + if (status != NT_STATUS_SUCCESS) { + logon->state = SDB_LSTATE_SETUP; + syslog(LOG_ERR, "smbrdr: logoff %s (%s)", logon->username, + xlate_nt_status(status)); + return (-1); + } + + rc = smb_msgbuf_encode(&srh.srh_mbuf, "bbbww", 2, 0xff, 0, 0, 0); + if (rc < 0) { + logon->state = SDB_LSTATE_SETUP; + smbrdr_handle_free(&srh); + syslog(LOG_ERR, "smbrdr: logoff %s (encode failed)", + logon->username); + return (rc); + } + + status = smbrdr_exchange(&srh, &smb_hdr, 0); + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "smbrdr: logoff %s (%s)", logon->username, + xlate_nt_status(status)); + rc = -1; + } else { + rc = 0; + } + + bzero(logon, sizeof (struct sdb_logon)); + smbrdr_handle_free(&srh); + return (rc); +} + + +/* + * smbrdr_logon_init + * + * Find a slot for account logon information. The account information + * is associated with a session so we need a valid session slot before + * calling this function. If we already have a record of the specified + * account, a pointer to that record is returned. Otherwise we attempt + * to allocate a new one. + */ +static struct sdb_logon * +smbrdr_logon_init(struct sdb_session *session, char *username, + char *pwd, int pwd_type) +{ + struct sdb_logon *logon; + int smbrdr_lmcomplvl; + int rc; + + logon = (struct sdb_logon *)malloc(sizeof (sdb_logon_t)); + if (logon == 0) + return (0); + + bzero(logon, sizeof (struct sdb_logon)); + logon->session = session; + + if (strcmp(username, "IPC$") == 0) + logon->type = SDB_LOGON_ANONYMOUS; + else + logon->type = SDB_LOGON_USER; + + (void) strlcpy(logon->username, username, MAX_ACCOUNT_NAME); + + smb_config_rdlock(); + smbrdr_lmcomplvl = smb_config_getnum(SMB_CI_LM_LEVEL); + smb_config_unlock(); + + switch (pwd_type) { + case SMBRDR_PWD_USER: + rc = smb_auth_set_info(username, pwd, 0, session->di.domain, + session->challenge_key, session->challenge_len, + smbrdr_lmcomplvl, &logon->auth); + + if (rc != 0) { + free(logon); + return (0); + } + break; + + case SMBRDR_PWD_HASH: + rc = smb_auth_set_info(username, 0, (unsigned char *)pwd, + session->di.domain, session->challenge_key, + session->challenge_len, smbrdr_lmcomplvl, &logon->auth); + + if (rc != 0) { + free(logon); + return (0); + } + break; + + case SMBRDR_PWD_NULL: + logon->auth.ci_len = 1; + *(logon->auth.ci) = 0; + logon->auth.cs_len = 0; + break; + + default: + /* Unknown password type */ + free(logon); + return (0); + } + + logon->state = SDB_LSTATE_INIT; + return (logon); +} + +/* + * smbrdr_logon_validate + * + * if session is there and it's alive and also the required + * user is already logged in don't need to do anything + * otherwise clear the session structure. + */ +static boolean_t +smbrdr_logon_validate(char *server, char *username) +{ + struct sdb_session *session; + boolean_t valid = B_FALSE; + + session = smbrdr_session_lock(server, username, SDB_SLCK_WRITE); + if (session) { + if (nb_keep_alive(session->sock) == 0) { + valid = B_TRUE; + } else { + session->state = SDB_SSTATE_STALE; + syslog(LOG_DEBUG, "smbrdr: (logon) stale session"); + } + + smbrdr_session_unlock(session); + } + + return (valid); +} diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_netbios.c b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_netbios.c new file mode 100644 index 0000000000..2066dab051 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_netbios.c @@ -0,0 +1,457 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * NetBIOS support functions. NetBIOS is documented in the following + * RFC documents: + * + * RFC 1001: Protocol Standard for a NetBIOS Service on a TCP/UDP + * Transport: Concepts and Methods + * + * RFC 1002: Protocol Standard for a NetBIOS Service on a TCP/UDP + * Transport: Detailed Specifications + * + */ + +#define BSD_BYTE_STRING_PROTOTYPES + +#include <string.h> +#include <unistd.h> +#include <syslog.h> +#include <synch.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/time.h> + +#include <stdio.h> +#include <pthread.h> + +#define MAX_NETBIOS_NAME_SIZE 16 + +#define SESSION_MESSAGE 0x00 +#define SESSION_REQUEST 0x81 +#define POSITIVE_SESSION_RESPONSE 0x82 +#define NEGATIVE_SESSION_RESPONSE 0x83 +#define RETARGET_SESSION_RESPONSE 0x84 +#define SESSION_KEEP_ALIVE 0x85 + +#define NB_READ_MSG_ERR_EOF 0 +#define NB_READ_MSG_ERR -1 +#define NB_READ_MSG_ERR_OVERFLOW -2 +#define NB_READ_MSG_ERR_UNDERFLOW -3 +#define NB_RCV_MSG_ERR_INVTYPE -4 + +/* + * Semaphore object used to serialize access through NetBIOS exchange. + */ +static mutex_t nb_mutex; + +static int nb_write_msg(int fd, unsigned char *buf, unsigned count, int type); +static int nb_read_msg(int fd, unsigned char *buf, unsigned max_buf, + int *type, long timeout); +static int nb_read_itter(int fd, unsigned char *buf, unsigned cnt); +static int nb_first_level_name_encode(char *name, char *scope, + unsigned char *out, int max_out); + + +/* + * nb_lock + * + * Acquire semaphore for doing netbios operations + */ +void +nb_lock() +{ + (void) mutex_lock(&nb_mutex); +} + +/* + * nb_lock + * + * Release netbios semaphore. + */ +void +nb_unlock() +{ + (void) mutex_unlock(&nb_mutex); +} + +void +nb_close(int fd) +{ + (void) mutex_lock(&nb_mutex); + if (fd > 0) { + (void) close(fd); + (void) printf("[%d] socket (%d) closed\n", pthread_self(), fd); + } + (void) mutex_unlock(&nb_mutex); +} + +/* + * nb_keep_alive + * + * Send the NetBIOS keep alive message. No response is expected but we + * do need to ignore keep-alive messages in nb_exchange. The semaphore + * ensures compatibility/serialization with nb_exchange to allow us to + * call this function from a separate thread. + */ +int +nb_keep_alive(int fd) +{ + int nothing; + int rc; + + (void) mutex_lock(&nb_mutex); + + rc = nb_write_msg(fd, (unsigned char *)¬hing, 0, SESSION_KEEP_ALIVE); + if (rc < 0) + syslog(LOG_ERR, "nb_keep_alive: write failed"); + + (void) mutex_unlock(&nb_mutex); + return (rc); +} + +/* + * nb_send + * + * This is just a wrapper round the nb_write_msg. + */ +int +nb_send(int fd, unsigned char *send_buf, unsigned send_cnt) +{ + int rc; + + if ((rc = nb_write_msg(fd, send_buf, send_cnt, SESSION_MESSAGE)) < 0) + syslog(LOG_ERR, "nb_send: write failed: rc=%d", rc); + + return (rc); +} + +/* + * nb_rcv + * + * This is a wrapper round the nb_read_msg() so that if a + * keep-alive message is received, just discard it and go + * back to look for the real response. + */ +int +nb_rcv(int fd, unsigned char *recv_buf, unsigned recv_max, long timeout) +{ + int rc; + int type; + + do { + rc = nb_read_msg(fd, recv_buf, recv_max, &type, timeout); + if (rc < 0) { + syslog(LOG_ERR, "nb_rcv: read failed: rc=%d", rc); + return (rc); + } + } while (type == SESSION_KEEP_ALIVE); + + if (type != SESSION_MESSAGE) { + syslog(LOG_ERR, "nb_rcv: invalid type: %d", type); + return (NB_RCV_MSG_ERR_INVTYPE); + } + + return (rc); +} + +/* + * nb_exchange + * + * This is the NetBIOS workhorse function where we do the send/receive + * message exchange. A semaphore is used to serialize access because + * we may get swapped out between the send and receive operations and + * another thread could enter here and collect our response. If a + * keep-alive message is received, just discard it and go back to look + * for the real response. + * + * Note: With the addition of support for SMB over TCP, this function + * may be exchanging NetBIOS-less SMB data. + */ +int +nb_exchange(int fd, unsigned char *send_buf, unsigned send_cnt, + unsigned char *recv_buf, unsigned recv_max, long timeout) +{ + int rc; + + (void) mutex_lock(&nb_mutex); + + rc = nb_send(fd, send_buf, send_cnt); + if (rc == send_cnt) + rc = nb_rcv(fd, recv_buf, recv_max, timeout); + + (void) mutex_unlock(&nb_mutex); + return (rc); +} + +/* + * nb_session_request + * + * We should never see descriptor 0 (stdin) or -1. + */ +int +nb_session_request(int fd, char *called_name, char *called_scope, + char *calling_name, char *calling_scope) +{ + unsigned char sr_buf[200]; + int len; + int rc; + int type; + + if (fd == 0 || fd == -1) + return (-1); + + rc = nb_first_level_name_encode(called_name, called_scope, sr_buf, 100); + len = rc; + rc = nb_first_level_name_encode(calling_name, calling_scope, + sr_buf+len, 100); + len += rc; + + (void) mutex_lock(&nb_mutex); + + rc = nb_write_msg(fd, (unsigned char *)sr_buf, len, SESSION_REQUEST); + if (rc < 0) { + syslog(LOG_ERR, "nb_session_request: write failed:" + " rc=%d", rc); + (void) mutex_unlock(&nb_mutex); + return (rc); + } + + for (;;) { + rc = nb_read_msg(fd, (unsigned char *)sr_buf, + sizeof (sr_buf), &type, 0); + if (rc < 0) { + (void) mutex_unlock(&nb_mutex); + return (rc); + } + + if ((rc == 0) && (type == -1)) { + (void) mutex_unlock(&nb_mutex); + return (-1); /* EOF */ + } + + if (type == POSITIVE_SESSION_RESPONSE) { + (void) mutex_unlock(&nb_mutex); + return (0); + } + + if (type == NEGATIVE_SESSION_RESPONSE) { + (void) mutex_unlock(&nb_mutex); + return (-1); + } + } + + /* NOTREACHED */ + (void) mutex_unlock(&nb_mutex); + return (-1); +} + + + +/* + * nb_write_msg + */ +static int +nb_write_msg(int fd, unsigned char *buf, unsigned count, int type) +{ + struct iovec iov[2]; + unsigned char header[4]; + int rc; + + if (fd == 0 || fd == -1) { + /* + * We should never see descriptor 0 (stdin). + */ + syslog(LOG_ERR, "nb_write_msg: invalid descriptor (%d)", fd); + return (-1); + } + + /* + * The NetBIOS message length is limited to 17 bits but + * we use this layer for SMB over both NetBIOS and TCP + * (NetBIOS-less SMB). When using SMB over TCP the length + * is 24 bits but we are ignoring that for now because we + * don't expect any messages larger than 64KB. + */ + header[0] = type; + header[1] = (count >> 16) & 1; + header[2] = count >> 8; + header[3] = count; + + iov[0].iov_base = (caddr_t)header; + iov[0].iov_len = 4; + iov[1].iov_base = (caddr_t)buf; + iov[1].iov_len = count; + + rc = writev(fd, iov, 2); + if (rc != 4 + count) { + syslog(LOG_ERR, "nb_write_msg: writev rc=%d", rc); + return (-3); /* error */ + } + + return (count); +} + + +/* + * nb_read_msg + * + * Added select to ensure that we don't block forever waiting for a + * message. + */ +static int +nb_read_msg(int fd, unsigned char *buf, unsigned max_buf, + int *type, long timeout) +{ + unsigned char header[4]; + int length; + int rc; + fd_set readfds; + struct timeval tval; + + *type = -1; + + if (fd == 0 || fd == -1) { + /* + * We should never see descriptor 0 (stdin). + */ + syslog(LOG_ERR, "nb_write_msg: invalid descriptor (%d)", fd); + return (NB_READ_MSG_ERR); + } + + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + tval.tv_sec = (timeout == 0) ? 45 : timeout; + tval.tv_usec = 0; + + if ((rc = select(fd + 1, &readfds, 0, 0, &tval)) <= 0) { + syslog(LOG_ERR, "nb_read_msg: select: %d", rc); + return (NB_READ_MSG_ERR); + } + + if ((rc = nb_read_itter(fd, header, 4)) < 0) + return (rc); /* error */ + + if (rc != 4) + return (NB_READ_MSG_ERR_EOF); /* EOF */ + + /* + * The NetBIOS message length is limited to 17 bits but + * we use this layer for SMB over both NetBIOS and TCP + * (NetBIOS-less SMB). When using SMB over TCP the length + * is 24 bits but we are ignoring that for now because we + * don't expect any messages larger than 64KB. + */ + *type = header[0]; + length = ((header[1]&1) << 16) + (header[2]<<8) + header[3]; + + if (length > max_buf) + return (NB_READ_MSG_ERR_OVERFLOW); /* error overflow */ + + if ((rc = nb_read_itter(fd, buf, length)) != length) + return (NB_READ_MSG_ERR_UNDERFLOW); /* error underflow */ + + return (rc); +} + + +/* + * nb_read_itter + * + * We should never see descriptor 0 (stdin) or -1. + */ +static int +nb_read_itter(int fd, unsigned char *buf, unsigned cnt) +{ + int ix; + int rc; + + for (ix = 0; ix < cnt; ix += rc) { + if (fd == 0 || fd == -1) + return (-1); + + if ((rc = read(fd, buf+ix, cnt-ix)) < 0) + return (rc); + + if (rc == 0) + break; + } + + return (ix); +} + + +/* + * nb_first_level_name_encode + */ +static int +nb_first_level_name_encode(char *name, char *scope, + unsigned char *out, int max_out) +{ + unsigned char ch, len; + unsigned char *in; + unsigned char *lp; + unsigned char *op = out; + unsigned char *op_end = op + max_out; + + in = (unsigned char *)name; + *op++ = 0x20; + for (len = 0; ((ch = *in) != 0) && len < MAX_NETBIOS_NAME_SIZE; + len++, in++) { + *op++ = 'A' + ((ch >> 4) & 0xF); + *op++ = 'A' + ((ch) & 0xF); + } + + for (; len < MAX_NETBIOS_NAME_SIZE; len++) { + ch = ' '; + *op++ = 'A' + ((ch >> 4) & 0xF); + *op++ = 'A' + ((ch) & 0xF); + } + + in = (unsigned char *)scope; + len = 0; + lp = op++; + for (; op < op_end; in++) { + ch = *in; + if (ch == 0) { + if ((*lp = len) != 0) + *op++ = 0; + break; + } + if (ch == '.') { + *lp = (op - lp) - 1; + lp = op++; + len = 0; + } else { + *op++ = ch; + len++; + } + } + + return ((int)(op - out)); +} diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_netuse.c b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_netuse.c new file mode 100644 index 0000000000..508b258cd6 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_netuse.c @@ -0,0 +1,458 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Tree connect and disconnect functions to support SMB shares. + * These functions are described in the CIFS draft 1.0 Protocol + * Specification (December 19, 1997). + */ + +#include <string.h> +#include <strings.h> +#include <syslog.h> +#include <synch.h> +#include <pthread.h> + +#include <smbsrv/libsmbrdr.h> + +#include <smbrdr.h> +#include <smbsrv/ntstatus.h> + + +/* + * The table of shares set up with the domain controller. + */ +static struct sdb_netuse netuse_table[N_NETUSE_TABLE]; + +static int smbrdr_smb_tcon(struct sdb_session *session, + struct sdb_netuse *netuse, char *path, int path_len); + +static struct sdb_netuse *smbrdr_netuse_alloc(struct sdb_session *session, + char *sharename); +static int smbrdr_smb_tdcon(struct sdb_netuse *netuse); + +static void +smbrdr_netuse_clear(struct sdb_netuse *netuse) +{ + bzero(netuse, sizeof (struct sdb_netuse) - sizeof (mutex_t)); +} + +static void +smbrdr_netuse_free(struct sdb_netuse *netuse) +{ + smbrdr_netuse_clear(netuse); + (void) mutex_unlock(&netuse->mtx); +} + +/* + * mlsvc_tree_connect + * + * Establish a share (tree connect). We need to retrieve the session + * for the specified host and allocate a netuse structure. We set up + * the path here (UNC encoded) to make handling the malloc/free easier + * and pass everything on to smbrdr_smb_tcon where, if everything goes well, + * a valid tid will be stored in the netuse structure. + * + * On success, a pointer to the netuse is returned. Otherwise the + * netuse is cleared and a null pointer is returned. + */ +unsigned short +mlsvc_tree_connect(char *hostname, char *username, char *sharename) +{ + struct sdb_session *session; + struct sdb_netuse *netuse; + char *path; + int path_len; + + /* + * Make sure there is a session & logon for given info + */ + session = smbrdr_session_lock(hostname, username, SDB_SLCK_READ); + if (session == 0) { + syslog(LOG_ERR, "smbrdr: (tcon) no session for %s@%s", + username, hostname); + return (0); + } + + + if ((netuse = smbrdr_netuse_alloc(session, sharename)) == 0) { + syslog(LOG_ERR, "smbrdr: (tcon) init failed"); + smbrdr_session_unlock(session); + return (0); + } + + /* + * Add some padding for the back-slash separators + * and the null-terminator. + */ + path_len = SMB_PI_MAX_HOST + MAX_SHARE_NAME + 5; + + if ((path = (char *)malloc(path_len)) == 0) { + smbrdr_netuse_free(netuse); + smbrdr_session_unlock(session); + syslog(LOG_ERR, "smbrdr: (tcon) resource shortage"); + return (0); + } + + bzero(path, path_len); + (void) snprintf(path, path_len, "\\\\%s\\%s", hostname, sharename); + if (session->remote_caps & CAP_UNICODE) + path_len = mts_wcequiv_strlen(path); + else + path_len = strlen(path); + + if (smbrdr_smb_tcon(session, netuse, path, path_len) < 0) { + smbrdr_netuse_free(netuse); + smbrdr_session_unlock(session); + free(path); + syslog(LOG_ERR, "smbrdr: (tcon) failed connecting to %s", path); + return (0); + } + + free(path); + (void) mutex_unlock(&netuse->mtx); + smbrdr_session_unlock(session); + return (netuse->tid); +} + + +/* + * smbrdr_smb_tcon + * + * This message requests a share (tree connect) request to the server + * associated with the session. The password is not relevant here if + * the session was establishment using setup_andx. The outgoing tid + * will be ignored - a valid one will be returned by the server. + * + * Returns 0 on success. Otherwise returns a -ve error code. + */ +static int +smbrdr_smb_tcon(struct sdb_session *session, struct sdb_netuse *netuse, + char *path, int path_len) +{ + smb_hdr_t smb_hdr; + smbrdr_handle_t srh; + smb_msgbuf_t *mb; + unsigned short flags; + char *password; + unsigned short password_len; + char *service; + unsigned service_len; + unsigned short data_bytes; + DWORD status; + int rc; + + status = smbrdr_request_init(&srh, SMB_COM_TREE_CONNECT_ANDX, + session, &session->logon, 0); + + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrTcon: %s", xlate_nt_status(status)); + return (-1); + } + + mb = &srh.srh_mbuf; + + flags = 0; /* no flags */ + password = ""; + password_len = 1; /* including nul */ + service = "?????"; /* does this work? */ + service_len = strlen(service); + + /* + * Calculate the BCC. The path is in UNICODE + * but the service is in ASCII. + */ + data_bytes = password_len; + data_bytes += path_len + 1; + data_bytes += service_len + 1; + + rc = smb_msgbuf_encode(mb, "bb1.wwww#cus", + 4, /* smb_wct */ + 0xff, /* AndXCommand (none) */ + 0xffff, /* AndXOffset */ + flags, /* Flags */ + password_len, /* PasswordLength */ + data_bytes+1, /* smb_bcc */ + password_len, password, /* Password */ + path, /* Path */ + service); /* Service */ + + if (rc <= 0) { + syslog(LOG_ERR, "smbrdr_smb_tcon: encode failed"); + smbrdr_handle_free(&srh); + return (-1); + } + + status = smbrdr_exchange(&srh, &smb_hdr, 0); + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrTcon: %s", xlate_nt_status(status)); + rc = -1; + } else { + rc = 0; + } + + netuse->tid = smb_hdr.tid; + netuse->state = SDB_NSTATE_CONNECTED; + smbrdr_handle_free(&srh); + return (rc); +} + + +/* + * smbrdr_netuse_logoff + * + * This function can be used when closing a session to ensure that all + * shares associated with the specified session are disconnected and + * the resources released. We also notify the pipe interface to ensure + * that any pipes associated with this share are also closed. This + * function silently ignores errors because we have no idea what state + * the session is in. We are more interested in releasing resources. + */ +void +smbrdr_netuse_logoff(unsigned short uid) +{ + struct sdb_netuse *netuse; + int i; + + for (i = 0; i < N_NETUSE_TABLE; ++i) { + netuse = &netuse_table[i]; + (void) mutex_lock(&netuse->mtx); + if (netuse->uid == uid) + (void) smbrdr_smb_tdcon(netuse); + (void) mutex_unlock(&netuse->mtx); + } +} + +int +smbrdr_tree_disconnect(unsigned short tid) +{ + struct sdb_netuse *netuse; + int rc = -1; + + netuse = smbrdr_netuse_get(tid); + if (netuse) { + (void) smbrdr_smb_tdcon(netuse); + smbrdr_netuse_put(netuse); + rc = 0; + } + + return (rc); +} + +/* + * smbrdr_smb_tdcon + * + * Disconnect a share. This message informs the server that we no longer + * wish to access the resource specified by tid, obtained via a prior + * mlsvc_tree_connect. The tid is passed in the SMB header so the setup + * for this call is very straightforward. + * + * Returns 0 on success. Otherwise returns a -ve error code. + */ +static int +smbrdr_smb_tdcon(struct sdb_netuse *netuse) +{ + struct sdb_session *session; + smbrdr_handle_t srh; + smb_hdr_t smb_hdr; + DWORD status; + int rc; + + netuse->state = SDB_NSTATE_DISCONNECTING; + smbrdr_ofile_end_of_share(netuse->tid); + + if ((session = netuse->session) == 0) { + smbrdr_netuse_clear(netuse); + return (0); + } + + if ((session->state != SDB_SSTATE_NEGOTIATED) && + (session->state != SDB_SSTATE_DISCONNECTING)) { + smbrdr_netuse_clear(netuse); + return (0); + } + + status = smbrdr_request_init(&srh, SMB_COM_TREE_DISCONNECT, + session, &session->logon, netuse); + + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "smbrdr: (tdcon) %s", xlate_nt_status(status)); + /* should we clear here? */ + smbrdr_netuse_clear(netuse); + return (-1); + } + + rc = smb_msgbuf_encode(&srh.srh_mbuf, "bw.", 0, 0); + if (rc < 0) { + syslog(LOG_ERR, "smbrdr: (tdcon) encode failed"); + smbrdr_handle_free(&srh); + /* should we clear here? */ + smbrdr_netuse_clear(netuse); + return (rc); + } + + status = smbrdr_exchange(&srh, &smb_hdr, 0); + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "smbrdr: (tdcon) %s", xlate_nt_status(status)); + rc = -1; + } else { + rc = 0; + } + + smbrdr_handle_free(&srh); + smbrdr_netuse_clear(netuse); + return (rc); +} + + +/* + * smbrdr_netuse_alloc + * + * Find a slot in the table for a share. Each share is associated with + * a session and assigned a local drive letter name and a sharename. + * If a slot is already allocated to the specified share, a pointer to + * it is returned. Otherwise we allocate and initialize a new slot in + * the table. If the table is full, a null pointer will be returned. + * + * IMPORTANT! the returned netuse will be locked caller has to unlock + * it after it's done with the pointer. + */ +static struct sdb_netuse * +smbrdr_netuse_alloc(struct sdb_session *session, char *sharename) +{ + struct sdb_netuse *netuse; + int i; + + if (session == 0 || sharename == 0) { + syslog(LOG_ERR, "smbrdr: (tcon) invalid arg"); + return (0); + } + + for (i = 0; i < N_NETUSE_TABLE; ++i) { + netuse = &netuse_table[i]; + + (void) mutex_lock(&netuse->mtx); + if (netuse->state == SDB_NSTATE_START) { + netuse->session = session; + netuse->letter = i + '0'; + netuse->sid = session->sid; + netuse->uid = session->logon.uid; + netuse->tid = 0; + (void) strcpy(netuse->share, sharename); + netuse->state = SDB_NSTATE_INIT; + return (netuse); + } + (void) mutex_unlock(&netuse->mtx); + } + + syslog(LOG_WARNING, "smbrdr: (tcon) table full"); + return (0); +} + +/* + * smbrdr_netuse_put + * + * Unlock given netuse structure. + */ +void +smbrdr_netuse_put(struct sdb_netuse *netuse) +{ + (void) mutex_unlock(&netuse->mtx); +} + +/* + * smbrdr_netuse_get + * + * Find the netuse structure associated with the specified tid and + * return a pointer to it. A null pointer is returned if no match + * can be found. + * + * IMPORTANT! the returned netuse will be locked caller has to unlock + * it after it's done with the pointer. + */ +struct sdb_netuse * +smbrdr_netuse_get(int tid) +{ + struct sdb_session *session; + struct sdb_netuse *netuse; + int i; + + for (i = 0; i < N_NETUSE_TABLE; ++i) { + netuse = &netuse_table[i]; + + (void) mutex_lock(&netuse->mtx); + + if (netuse->tid == tid) { + session = netuse->session; + + /* + * status check: + * make sure all the structures are in the right state + */ + if (session && + (netuse->state == SDB_NSTATE_CONNECTED) && + (session->logon.state == SDB_LSTATE_SETUP) && + (session->state == SDB_SSTATE_NEGOTIATED)) { + /* sanity check */ + if ((netuse->sid == session->sid) && + (netuse->uid == session->logon.uid)) + return (netuse); + else + /* invalid structure */ + smbrdr_netuse_clear(netuse); + } + + } + + (void) mutex_unlock(&netuse->mtx); + } + + syslog(LOG_WARNING, "smbrdr: (lookup) no such TID %d", tid); + return (0); +} + +/* + * smbrdr_dump_netuse + */ +void +smbrdr_dump_netuse() +{ + struct sdb_netuse *netuse; + int i; + + for (i = 0; i < N_NETUSE_TABLE; ++i) { + netuse = &netuse_table[i]; + (void) mutex_lock(&netuse->mtx); + if (netuse->session) { + syslog(LOG_DEBUG, "tree[%d]: %s (tid=%d)", i, + netuse->share, netuse->tid); + syslog(LOG_DEBUG, "tree[%d]: session(%d), user(%d)", + i, netuse->session->sock, netuse->uid); + } + (void) mutex_unlock(&netuse->mtx); + } +} diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_read_andx.c b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_read_andx.c new file mode 100644 index 0000000000..ef90fa79ac --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_read_andx.c @@ -0,0 +1,210 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * SMB ReadX functions to support MLRPC. + */ + +#include <syslog.h> +#include <strings.h> + +#include <smbsrv/libsmbrdr.h> + +#include <smbsrv/netbios.h> +#include <smbsrv/ntstatus.h> +#include <smbrdr.h> + +#define SMBRDR_READX_RSP_OVERHEAD \ + (NETBIOS_HDR_SZ + SMB_HEADER_LEN + sizeof (smb_read_andx_rsp_t)) +#define SMBRDR_READX_RSP_DATA_MAXLEN \ + (SMBRDR_REQ_BUFSZ - SMBRDR_READX_RSP_OVERHEAD) + +static int smbrdr_decode_readx_rsp(smb_msgbuf_t *, char *, unsigned, + smb_read_andx_rsp_t *); + +static void smbrdr_dump_readx_rsp(smb_read_andx_rsp_t *); + +/* + * smbrdr_rpc_readx + * + * Send SMB_COM_READ_ANDX request. + */ +int +smbrdr_rpc_readx(int fid, char *in_buf, int in_len) +{ + struct sdb_netuse *netuse; + struct sdb_ofile *ofile; + smb_read_andx_rsp_t rsp; + smbrdr_handle_t srh; + smb_msgbuf_t *mb; + DWORD status; + int rc, max_return; + + if ((ofile = smbrdr_ofile_get(fid)) == 0) + return (-1); + + netuse = ofile->netuse; + + status = smbrdr_request_init(&srh, SMB_COM_READ_ANDX, + netuse->session, &netuse->session->logon, netuse); + + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrReadAndx: %s", xlate_nt_status(status)); + smbrdr_ofile_put(ofile); + return (-1); + } + + mb = &(srh.srh_mbuf); + + max_return = (in_len > SMBRDR_READX_RSP_DATA_MAXLEN) ? + SMBRDR_READX_RSP_DATA_MAXLEN : in_len; + + rc = smb_msgbuf_encode(mb, "bbbwwlwwlwlw", + 12, /* Count of parameter words */ + 0xFF, /* Secondary (X) command; 0xFF = none */ + 0, /* Reserved (must be 0) */ + 0, /* Offset to next command WordCount */ + ofile->fid, /* File handle */ + 0, /* Offset in file to begin read */ + max_return, /* Max number of bytes to return */ + /* Reserved for obsolescent requests [0 = non-blocking read] */ + max_return, + /* + * High 16 bits of MaxCount if CAP_LARGE_READX; + * else MUST BE ZERO + */ + 0, + max_return, /* Reserved for obsolescent requests */ + /* Upper 32 bits of offset (only if WordCount is 12) */ + 0, + 0); /* Count of data bytes = 0 */ + + if (rc < 0) { + syslog(LOG_ERR, "SmbrdrReadAndx: smbrdr_prep_readx_req failed"); + smbrdr_handle_free(&srh); + smbrdr_ofile_put(ofile); + return (rc); + } + + smbrdr_lock_transport(); + + status = smbrdr_send(&srh); + if (status != NT_STATUS_SUCCESS) { + smbrdr_unlock_transport(); + smbrdr_handle_free(&srh); + smbrdr_ofile_put(ofile); + syslog(LOG_ERR, "SmbrdrReadAndx: send failed"); + return (-1); + } + + status = smbrdr_rcv(&srh, 1); + + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrReadAndx: nb_rcv failed"); + smbrdr_unlock_transport(); + smbrdr_handle_free(&srh); + smbrdr_ofile_put(ofile); + return (-1); + } + + rc = smbrdr_decode_readx_rsp(mb, in_buf, in_len, &rsp); + + if (rc < 0) { + syslog(LOG_ERR, "SmbrdrReadAndx: read decode failure!"); + smbrdr_unlock_transport(); + smbrdr_handle_free(&srh); + smbrdr_ofile_put(ofile); + return (-1); + } + + smbrdr_unlock_transport(); + smbrdr_handle_free(&srh); + smbrdr_ofile_put(ofile); + + return ((rc < 0) ? rc : rsp.DataLength); +} + +static void +smbrdr_dump_readx_rsp(smb_read_andx_rsp_t *rsp) +{ + + syslog(LOG_DEBUG, "[SmbReadX Rsp] WordCount:%x,AndXCmd:%x," + " AndXReserved:%x, AndXOffset:%d", + rsp->WordCount, rsp->AndXCmd, rsp->AndXReserved, rsp->AndXOffset); + + syslog(LOG_DEBUG, "[SmbReadX Rsp] Remaining:%d, Mode:%d, Reserved:%x, " + "DataLen:%d, DataOffset:%d, ByteCount: %d", + rsp->Remaining, rsp->DataCompactionMode, rsp->Reserved, + rsp->DataLength, rsp->DataOffset, rsp->ByteCount); +} + +/* + * smbrdr_decode_readx_rsp + * + * Decode the response from the SMB_COM_READ_ANDX request. The payload + * of the response is appended to the end of SmbTransact response data + * in the MLRPC receive buffer. + * + * Return -1 on error, 0 upon success. + */ +static int +smbrdr_decode_readx_rsp(smb_msgbuf_t *mb, + char *in, + unsigned in_len, + smb_read_andx_rsp_t *rsp) +{ + int rc; + + rc = smb_msgbuf_decode(mb, "bbbwwwwwwlwwww", + &rsp->WordCount, + &rsp->AndXCmd, + &rsp->AndXReserved, + &rsp->AndXOffset, + &rsp->Remaining, + &rsp->DataCompactionMode, + &rsp->Reserved, + &rsp->DataLength, + &rsp->DataOffset, + &rsp->DataLengthHigh, + &rsp->Reserved2[0], + &rsp->Reserved2[1], + &rsp->Reserved2[2], + &rsp->ByteCount); + + if (rc <= 0) + return (-1); + + smbrdr_dump_readx_rsp(rsp); + + /* it should never happen, but check anyway */ + if (rsp->DataLength > in_len) + return (-1); + + bcopy(mb->base + rsp->DataOffset, in, rsp->DataLength); + + return (0); +} diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_rpcpipe.c b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_rpcpipe.c new file mode 100644 index 0000000000..b89eddf275 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_rpcpipe.c @@ -0,0 +1,518 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Functions to open and close named pipes. These functions are + * described in the CIFS 1.0 Protocol Specification (December 19, 1997). + */ + +#include <alloca.h> +#include <pthread.h> +#include <string.h> +#include <strings.h> +#include <syslog.h> +#include <synch.h> + +#include <smbsrv/libsmbrdr.h> + +#include <smbsrv/ntstatus.h> +#include <smbrdr.h> + +static int smbrdr_smb_close(struct sdb_ofile *ofile); +static DWORD smbrdr_smb_ntcreate(struct sdb_ofile *ofile); +static struct sdb_ofile *smbrdr_ofile_alloc(struct sdb_netuse *netuse, + char *name); + +static void +smbrdr_ofile_clear(struct sdb_ofile *ofile) +{ + bzero(ofile, sizeof (struct sdb_ofile) - sizeof (mutex_t)); +} + +static void +smbrdr_ofile_free(struct sdb_ofile *ofile) +{ + smbrdr_ofile_clear(ofile); + (void) mutex_unlock(&ofile->mtx); +} + + +/* + * The ofile table. + */ +static struct sdb_ofile ofile_table[N_OFILE_TABLE]; + +static int mlsvc_pipe_recon_wait = 50; +static int mlsvc_pipe_recon_tries = 3; + + +/* + * mlsvc_open_pipe + * + * Open an RPC pipe on hostname. On success, return the fid. Otherwise + * returns a -ve error code. + */ +int +mlsvc_open_pipe(char *hostname, char *domain, char *username, char *pipename) +{ + struct sdb_netuse *netuse; + struct sdb_ofile *ofile; + unsigned short tid; + DWORD status; + int retry; + struct timespec st; + + tid = mlsvc_tree_connect(hostname, username, "IPC$"); + if (tid == 0) { + syslog(LOG_ERR, "smbrdr: (open) %s %s %s %s %s", + hostname, domain, username, pipename, + xlate_nt_status(NT_STATUS_UNEXPECTED_NETWORK_ERROR)); + return (-1); + } + + netuse = smbrdr_netuse_get(tid); + if (netuse == 0) { + syslog(LOG_ERR, "smbrdr: (open) %s %s %s %s %s", + hostname, domain, username, pipename, + xlate_nt_status(NT_STATUS_CONNECTION_INVALID)); + return (-1); + } + + if ((ofile = smbrdr_ofile_alloc(netuse, pipename)) == 0) { + syslog(LOG_ERR, "smbrdr: (open) %s %s %s %s %s", + hostname, domain, username, pipename, + xlate_nt_status(NT_STATUS_INSUFFICIENT_RESOURCES)); + smbrdr_netuse_put(netuse); + return (-1); + } + + status = NT_STATUS_OPEN_FAILED; + + for (retry = 0; retry < mlsvc_pipe_recon_tries; retry++) { + status = smbrdr_smb_ntcreate(ofile); + + switch (status) { + case NT_STATUS_SUCCESS: + (void) mutex_unlock(&ofile->mtx); + smbrdr_netuse_put(netuse); + return (ofile->fid); + + case NT_STATUS_PIPE_NOT_AVAILABLE: + case NT_STATUS_PIPE_BUSY: + /* + * The server might return this error if it is + * temporarily busy or unable to create a pipe. + * We wait here before trying again to see if + * the pipe becomes available. + */ + st.tv_sec = 0; + st.tv_nsec = mlsvc_pipe_recon_wait * 1000000; + (void) nanosleep(&st, 0); + break; + + default: + /* + * Something else went wrong: no more retries. + */ + retry = mlsvc_pipe_recon_tries; + break; + } + } + + syslog(LOG_DEBUG, "smbrdr: (open) %s %s %s %s %s", + hostname, domain, username, pipename, + xlate_nt_status(status)); + smbrdr_ofile_free(ofile); + smbrdr_netuse_put(netuse); + return (-1); +} + +/* + * mlsvc_close_pipe + * + * Close the named pipe represented by fid. + */ +int +mlsvc_close_pipe(int fid) +{ + struct sdb_ofile *ofile; + unsigned short tid; + int rc; + + if ((ofile = smbrdr_ofile_get(fid)) == 0) { + syslog(LOG_ERR, "mlsvc_close_pipe: unknown file (%d)", fid); + return (-1); + } + + tid = ofile->tid; + rc = smbrdr_smb_close(ofile); + smbrdr_ofile_put(ofile); + + if (rc == 0) + (void) smbrdr_tree_disconnect(tid); + + return (rc); +} + +/* + * smbrdr_ofile_put + * + * Unlock given ofile structure. + */ +void +smbrdr_ofile_put(struct sdb_ofile *ofile) +{ + if (ofile) + (void) mutex_unlock(&ofile->mtx); +} + +/* + * smbrdr_ofile_get + * + * Locate the ofile for the specified fid. Just to be safe, ensure that + * the netuse pointer is valid. Return a pointer to the ofile structure. + * Return a null pointer if a valid ofile cannot be found. + */ +struct sdb_ofile * +smbrdr_ofile_get(int fid) +{ + struct sdb_session *session; + struct sdb_netuse *netuse; + struct sdb_ofile *ofile; + int i; + + for (i = 0; i < N_OFILE_TABLE; ++i) { + ofile = &ofile_table[i]; + + (void) mutex_lock(&ofile->mtx); + + if (ofile->fid == fid) { + session = ofile->session; + netuse = ofile->netuse; + + /* + * status check: + * make sure all the structures are in the right state + */ + if (session && netuse && + (ofile->state == SDB_FSTATE_OPEN) && + (netuse->state == SDB_NSTATE_CONNECTED) && + (session->logon.state == SDB_LSTATE_SETUP) && + (session->state == SDB_SSTATE_NEGOTIATED)) { + /* sanity check */ + if ((ofile->sid == session->sid) && + (ofile->uid == session->logon.uid) && + (ofile->tid == netuse->tid)) { + return (ofile); + } else { + /* invalid structure */ + smbrdr_ofile_clear(ofile); + } + } + } + + (void) mutex_unlock(&ofile->mtx); + } + + syslog(LOG_WARNING, "smbrdr: (lookup) no such FID %d", fid); + return (0); +} + +/* + * smbrdr_ofile_end_of_share + * + * This function can be used when closing a share to ensure that all + * ofiles resources are released. Don't call mlsvc_close_pipe because + * that will call mlsvc_smb_tdcon and we don't know what state + * the share is in. The server will probably close all files anyway. + * We are more interested in releasing the ofile resources. + */ +void +smbrdr_ofile_end_of_share(unsigned short tid) +{ + struct sdb_ofile *ofile; + int i; + + for (i = 0; i < N_OFILE_TABLE; ++i) { + ofile = &ofile_table[i]; + (void) mutex_lock(&ofile->mtx); + if (ofile->tid == tid) + (void) smbrdr_smb_close(ofile); + (void) mutex_unlock(&ofile->mtx); + } +} + +/* + * smbrdr_dump_ofiles + * + * Dump the open files table. + */ +void +smbrdr_dump_ofiles() +{ + struct sdb_ofile *ofile; + struct sdb_netuse *netuse; + int i; + + for (i = 0; i < N_OFILE_TABLE; ++i) { + ofile = &ofile_table[i]; + (void) mutex_lock(&ofile->mtx); + netuse = ofile->netuse; + + if (netuse) { + syslog(LOG_DEBUG, "file[%d]: %s (fid=%d)", i, + ofile->path, ofile->fid); + syslog(LOG_DEBUG, + "file[%d]: session(%d), user(%d), tree(%d)", + i, netuse->session->sock, netuse->uid, + netuse->tid); + } + (void) mutex_unlock(&ofile->mtx); + } +} + +/* + * Private Functions + */ + +/* + * smbrdr_smb_close + * + * Send SMBClose request for the given open file. + */ +static int +smbrdr_smb_close(struct sdb_ofile *ofile) +{ + struct sdb_session *session; + struct sdb_netuse *netuse; + struct sdb_logon *logon; + smbrdr_handle_t srh; + smb_hdr_t smb_hdr; + DWORD status; + int fid; + int rc; + + if (ofile == 0) + return (0); + + ofile->state = SDB_FSTATE_CLOSING; + + if ((session = ofile->session) == 0) { + smbrdr_ofile_clear(ofile); + return (0); + } + + if ((session->state != SDB_SSTATE_NEGOTIATED) && + (session->state != SDB_SSTATE_DISCONNECTING)) { + smbrdr_ofile_clear(ofile); + return (0); + } + + fid = ofile->fid; + + netuse = ofile->netuse; + logon = &session->logon; + + status = smbrdr_request_init(&srh, SMB_COM_CLOSE, + session, logon, netuse); + + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrClose: %s", xlate_nt_status(status)); + smbrdr_ofile_clear(ofile); + return (-1); + } + + rc = smb_msgbuf_encode(&srh.srh_mbuf, + "(wct)b (fid)w (lwrtm)l (bcc)w (pad).", + 3, /* WordCount */ + fid, /* Fid */ + 0x00000000ul, /* LastWriteTime */ + 0); /* ByteCount */ + + if (rc <= 0) { + syslog(LOG_ERR, "SmbrdrClose: encode failed"); + smbrdr_handle_free(&srh); + smbrdr_ofile_clear(ofile); + return (-1); + } + + status = smbrdr_exchange(&srh, &smb_hdr, 0); + if (status != NT_STATUS_SUCCESS) + syslog(LOG_ERR, "SmbrdrClose: %s", xlate_nt_status(status)); + + smbrdr_handle_free(&srh); + smbrdr_ofile_clear(ofile); + return (0); +} + +/* + * smbrdr_ofile_alloc + * + * Allocate an ofile for the specified name. File info is associated + * with a share so we need a valid share before calling this function. + * If a slot is already allocated to the specified file, a pointer to + * that slot is returned. Otherwise we allocate and initialize a new + * slot in the table. If the table is full, a null pointer will be + * returned. + */ +static struct sdb_ofile * +smbrdr_ofile_alloc(struct sdb_netuse *netuse, char *name) +{ + struct sdb_ofile *ofile; + int i; + + for (i = 0; i < N_OFILE_TABLE; ++i) { + ofile = &ofile_table[i]; + + (void) mutex_lock(&ofile->mtx); + if (ofile->netuse == 0) { + + ofile->session = netuse->session; + ofile->netuse = netuse; + ofile->sid = netuse->session->sid; + ofile->uid = netuse->session->logon.uid; + ofile->tid = netuse->tid; + ofile->fid = 0; + (void) strcpy(ofile->path, name); + ofile->state = SDB_FSTATE_INIT; + return (ofile); + } + + (void) mutex_unlock(&ofile->mtx); + } + + syslog(LOG_WARNING, "smbrdr: (open) table full"); + return (0); +} + +/* + * smbrdr_smb_ntcreate + * + * This will do an SMB_COM_NT_CREATE_ANDX with lots of default values. + * All of the underlying session and share data should already be set + * up before we get here. If everything works we'll get a valid fid. + */ +static DWORD +smbrdr_smb_ntcreate(struct sdb_ofile *ofile) +{ + struct sdb_logon *logon; + struct sdb_netuse *netuse; + struct sdb_session *sess; + smbrdr_handle_t srh; + smb_hdr_t smb_hdr; + smb_msgbuf_t *mb; + char *path; + unsigned path_len; + int data_bytes; + int rc; + unsigned short fid; + int null_size; + DWORD status; + + netuse = ofile->netuse; + sess = netuse->session; + logon = &sess->logon; + + /* + * If this was a general purpose interface, we should support + * full UNC semantics but we only use this for RPC over named + * pipes with well-known endpoints. + */ + path_len = strlen(ofile->path) + 2; + path = alloca(path_len); + + if (ofile->path[0] != '\\') + (void) snprintf(path, path_len, "\\%s", ofile->path); + else + (void) strcpy(path, ofile->path); + + if (sess->remote_caps & CAP_UNICODE) { + path_len = mts_wcequiv_strlen(path); + null_size = sizeof (mts_wchar_t); + } else { + path_len = strlen(path); + null_size = sizeof (char); + } + + syslog(LOG_DEBUG, "SmbRdrNtCreate: %d %s", path_len, path); + + status = smbrdr_request_init(&srh, SMB_COM_NT_CREATE_ANDX, + sess, logon, netuse); + + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrNtCreate: %s", xlate_nt_status(status)); + return (NT_STATUS_INVALID_PARAMETER_1); + } + + mb = &srh.srh_mbuf; + + data_bytes = path_len + null_size; + + rc = smb_msgbuf_encode(mb, + "(wct)b (andx)b1.w (resv). (nlen)w (flg)l" + "(rdf)l (dacc)l (allo)q (efa)l (shr)l (cdisp)l (copt)l (impl)l" + "(secf)b (bcc)w (name)u", + 24, /* smb_wct */ + 0xff, /* AndXCommand (none) */ + 0x0000, /* AndXOffset */ + path_len, /* Unicode NameLength */ + 0x00000006ul, /* Flags (oplocks) */ + 0, /* RootDirectoryFid */ + 0x0002019Ful, /* DesiredAccess */ + 0x0ull, /* AllocationSize */ + 0x00000000ul, /* ExtFileAttributes */ + 0x00000003ul, /* ShareAccess (RW) */ + 0x00000001ul, /* CreateDisposition (OpenExisting) */ + 0x00000000ul, /* CreateOptions */ + 0x00000002ul, /* ImpersonationLevel */ + 0x01u, /* SecurityFlags */ + data_bytes, /* smb_bcc */ + path); /* Name */ + + if (rc <= 0) { + smbrdr_handle_free(&srh); + return (NT_STATUS_INVALID_PARAMETER_1); + } + + status = smbrdr_exchange(&srh, &smb_hdr, 0); + if (status != NT_STATUS_SUCCESS) { + smbrdr_handle_free(&srh); + return (NT_SC_VALUE(status)); + } + + rc = smb_msgbuf_decode(mb, "(wct). (andx)4. (opl)1. (fid)w", &fid); + if (rc <= 0) { + smbrdr_handle_free(&srh); + return (NT_STATUS_INVALID_PARAMETER_2); + } + + ofile->fid = fid; + ofile->state = SDB_FSTATE_OPEN; + syslog(LOG_DEBUG, "SmbRdrNtCreate: fid=%d", ofile->fid); + smbrdr_handle_free(&srh); + return (NT_STATUS_SUCCESS); +} diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_session.c b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_session.c new file mode 100644 index 0000000000..535ad537d9 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_session.c @@ -0,0 +1,889 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module provides the netbios and SMB negotiation, connect and + * disconnect interface. + */ + +#include <unistd.h> +#include <syslog.h> +#include <synch.h> +#include <string.h> +#include <strings.h> +#include <pthread.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <inttypes.h> +#include <netdb.h> + +#include <smbsrv/libsmbrdr.h> +#include <smbsrv/netbios.h> +#include <smbsrv/cifs.h> + +#include <smbsrv/ntstatus.h> +#include <smbsrv/mlsvc.h> +#include <smbrdr.h> +#include <smbrdr_ipc_util.h> + + +static uint16_t smbrdr_ports[] = { + SMB_SRVC_TCP_PORT, + SSN_SRVC_TCP_PORT +}; + +static int smbrdr_nports = sizeof (smbrdr_ports) / sizeof (smbrdr_ports[0]); + +/* + * Pointer to the PDC location interface. + * To be set up by SMB when it loads. + */ +static mlsvc_locate_pdc_t mlsvc_locate_pdc; + +/* + * This is a temporary hack to stop the DC from closing a session + * due to inactivity. + */ +#define MLSVC_SESSION_FORCE_KEEPALIVE 10 + +/* + * This is the session data table. + * + * The rwlock synchronizes access to the session table + * + * The mutex is to make session lookup and create atomic + * so we don't end up with two sessions with the same + * system. + */ +static struct sdb_session session_table[MLSVC_DOMAIN_MAX]; +static mutex_t smbrdr_screate_mtx; +static unsigned int session_id = 0; + +static struct sdb_session *smbrdr_session_init(smb_ntdomain_t *di); +static int smbrdr_trnsprt_connect(struct sdb_session *, uint16_t); +static int smbrdr_session_connect(smb_ntdomain_t *di); +static int smbrdr_smb_negotiate(struct sdb_session *session); +static int smbrdr_smb_echo(struct sdb_session *session); +static void smbrdr_session_disconnect(struct sdb_session *session, int cleanup); +static int smbrdr_locate_dc(char *domain); + +static void +smbrdr_session_clear(struct sdb_session *session) +{ + bzero(session, sizeof (struct sdb_session) - sizeof (rwlock_t)); +} + +/* + * mlsvc_install_pdc_cb + * + * Function to be called by SMB initialization code to set up a + * callback to the PDC location interface. + */ +void +mlsvc_install_pdc_cb(mlsvc_locate_pdc_t locate_pdc_cb) +{ + mlsvc_locate_pdc = locate_pdc_cb; +} + +/* + * mlsvc_locate_domain_controller + * + * Locate a domain controller. Note that this may close an existing + * connection to the current domain controller. + */ +int +mlsvc_locate_domain_controller(char *domain) +{ + if (mlsvc_locate_pdc) + return (mlsvc_locate_pdc(domain)); + + return (0); +} + +/* + * Entry pointy for smbrdr initialization. + */ +void +smbrdr_init(void) +{ + smbrdr_ipc_init(); +} + +/* + * mlsvc_disconnect + * + * Disconnects the session with given server. + */ +void +mlsvc_disconnect(char *server) +{ + struct sdb_session *session; + + session = smbrdr_session_lock(server, 0, SDB_SLCK_WRITE); + if (session) { + smbrdr_session_disconnect(session, 0); + smbrdr_session_unlock(session); + } +} + +/* + * smbrdr_negotiate + * + * Negotiate a session with a domain controller in the specified domain. + * The domain must be one of values from the smbinfo that indicates the + * resource domain or the account domain. + * + * If a session already exists, we can use that one. Otherwise we create + * a new one. This sets up the session key and session security info that + * we'll need later to authenticate the user. The session security info + * is returned to support the SMB client pass-through authentication + * interface. + * + * Returns 0 on success, otherwise -1. + */ +int +smbrdr_negotiate(char *domain_name) +{ + struct sdb_session *session = 0; + smb_ntdomain_t *di; + int retry = 1; + int res = 0; + + if ((di = smb_getdomaininfo(0)) == 0) { + /* + * Attempting to locate a domain controller + * will shutdown an existing PDC connection. + */ + (void) smbrdr_locate_dc(domain_name); + di = smb_getdomaininfo(0); + } + + if (di == 0) { + syslog(LOG_ERR, "smbrdr: negotiate (cannot access domain)"); + return (-1); + } + + /* + * The mutex is to make session lookup and create atomic + * so we don't end up with two sessions with the same + * server. + */ + (void) mutex_lock(&smbrdr_screate_mtx); + while (retry > 0) { + session = smbrdr_session_lock(di->server, 0, SDB_SLCK_WRITE); + if (session != 0) { + if (nb_keep_alive(session->sock) == 0) { + /* session is good, use it */ + smbrdr_session_unlock(session); + break; + } else { + /* stale session */ + session->state = SDB_SSTATE_STALE; + smbrdr_session_unlock(session); + } + } + + if (smbrdr_session_connect(di) != 0) { + if (retry > 0) { + /* Do we really need to do this here? */ + (void) smbrdr_locate_dc(domain_name); + di = smb_getdomaininfo(0); + if (di == 0) { + syslog(LOG_ERR, "smbrdr: negotiate" + " (cannot access domain)"); + res = -1; + break; + } + retry--; + } + } else { + /* session is created */ + retry = 0; + } + } + (void) mutex_unlock(&smbrdr_screate_mtx); + + return (res); +} + +/* + * smbrdr_session_connect + * + * This is the entry point for establishing an SMB connection to a + * domain controller. A session structure is allocated, a netbios + * session is set up and the SMB protocol is negotiated. If this is + * successful, the returned session structure can be used to logon + * to the the domain. A null pointer is returned if the connect fails. + */ +static int +smbrdr_session_connect(smb_ntdomain_t *di) +{ + struct sdb_session *session; + uint16_t port; + int rc = 0; + + /* + * smbrdr_session_init() will lock the session so that it wouldn't + * be accessible until it's established otherwise another thread + * might get access to a session which is not fully established. + */ + if ((session = smbrdr_session_init(di)) == 0) { + syslog(LOG_ERR, "smbrdr: session init failed"); + return (-1); + } + + for (port = 0; port < smbrdr_nports; ++port) { + syslog(LOG_DEBUG, "smbrdr: trying port %d", + smbrdr_ports[port]); + + rc = smbrdr_trnsprt_connect(session, smbrdr_ports[port]); + + if (rc == 0) { + syslog(LOG_DEBUG, "smbrdr: connected port %d", + smbrdr_ports[port]); + break; + } + } + + if (rc < 0) { + smbrdr_session_clear(session); + smbrdr_session_unlock(session); + syslog(LOG_ERR, "smbrdr: NBT/TCP connect failed"); + return (-1); + } + + if (smbrdr_smb_negotiate(session) < 0) { + (void) close(session->sock); + smbrdr_session_clear(session); + smbrdr_session_unlock(session); + syslog(LOG_ERR, "smbrdr: SMB negotiate failed"); + return (-1); + } + + smbrdr_session_unlock(session); + return (0); +} + + +/* + * smbrdr_trnsprt_connect + * + * Set up the TCP/IP and NETBIOS protocols for a session. This is just + * standard socket sutff. The paranoia check for socket descriptor 0 + * is because we had a problem with this value and the console telnet + * interface will lock up if we use and/or close stdin (0). + * + * Return 0 on success. Otherwise return (-1) to indicate a problem. + */ +static int +smbrdr_trnsprt_connect(struct sdb_session *sess, uint16_t port) +{ + char hostname[MAXHOSTNAMELEN]; + struct sockaddr_in sin; + int sock, rc; + mts_wchar_t unicode_server_name[SMB_PI_MAX_DOMAIN]; + char server_name[SMB_PI_MAX_DOMAIN]; + unsigned int cpid = oem_get_smb_cpid(); + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) <= 0) { + /* + * We should never see descriptor 0 (stdin). + */ + syslog(LOG_ERR, "smbrdr: socket(%d) failed (%s)", sock, + strerror(errno)); + return (-1); + } + + bzero(&sin, sizeof (struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = sess->di.ipaddr; + sin.sin_port = htons(port); + + if ((rc = connect(sock, (struct sockaddr *)&sin, sizeof (sin))) < 0) { + syslog(LOG_ERR, "smbrdr: connect failed (%s)", strerror(errno)); + if (sock != 0) + (void) close(sock); + return (-1); + } + + (void) mts_mbstowcs(unicode_server_name, sess->di.server, + SMB_PI_MAX_DOMAIN); + rc = unicodestooems(server_name, unicode_server_name, + SMB_PI_MAX_DOMAIN, cpid); + if (rc == 0) { + syslog(LOG_ERR, "smbrdr: unicode conversion failed"); + if (sock != 0) + (void) close(sock); + return (-1); + } + + /* + * If we are using NetBIOS, we need to set up a NETBIOS session. + * This typically implies that we will be using port 139. + * Otherwise, we're doing NetBIOS-less SMB, i.e. SMB over TCP, + * which is typically on port 445. + */ + if (port == SSN_SRVC_TCP_PORT) { + if (smb_getnetbiosname(hostname, MAXHOSTNAMELEN) != 0) { + syslog(LOG_ERR, "smbrdr: no hostname"); + if (sock != 0) + (void) close(sock); + return (-1); + } + + rc = nb_session_request(sock, + server_name, sess->scope, hostname, sess->scope); + + if (rc != 0) { + syslog(LOG_ERR, + "smbrdr: NBT session request to %s failed %d", + server_name, rc); + if (sock != 0) + (void) close(sock); + return (-1); + } + } + + sess->sock = sock; + sess->port = port; + syslog(LOG_DEBUG, "smbrdr: connected on port %d", port); + sess->state = SDB_SSTATE_CONNECTED; + return (0); +} + +/* + * smbrdr_smb_negotiate + * + * Negotiate the protocol we are going to use as described in CIFS + * section 4.1.1. The only protocol we support is NT LM 0.12, so we + * really expect to see dialect 0 in the response. The only other + * data gathered is the session key. + * + * Negotiate using ASCII strings. + * + * Return 0 on success. Otherwise return a -ve error code. + */ +static int +smbrdr_smb_negotiate(struct sdb_session *sess) +{ + unsigned short dialect; + smbrdr_handle_t srh; + smb_hdr_t smb_hdr; + smb_msgbuf_t *mb; + DWORD status; + int rc; + uint8_t tmp_secmode; + uint8_t tmp_clen; + + status = smbrdr_request_init(&srh, SMB_COM_NEGOTIATE, sess, 0, 0); + + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "smbrdr: negotiate (%s)", + xlate_nt_status(status)); + return (-1); + } + + mb = &srh.srh_mbuf; + rc = smb_msgbuf_encode(mb, "(wct)b (bcc)w (dialect)bs", + 0, /* smb_wct */ + 12, /* smb_bcc */ + 0x02, /* dialect marker */ + "NT LM 0.12"); /* only dialect we care about */ + + if (rc <= 0) { + syslog(LOG_ERR, "smbrdr: negotiate (encode failed)"); + smbrdr_handle_free(&srh); + return (-1); + } + + status = smbrdr_exchange(&srh, &smb_hdr, 0); + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "smbrdr: negotiate (%s)", + xlate_nt_status(status)); + smbrdr_handle_free(&srh); + return (-1); + } + + sess->secmode = 0; + sess->sesskey = 0; + sess->challenge_len = 0; + + rc = smb_msgbuf_decode(mb, + "(wordcnt)1.(dialect)w(secm)b12.(skey)l(cap)l10.(klen)b2.", + &dialect, &tmp_secmode, &sess->sesskey, &sess->remote_caps, + &tmp_clen); + + if (rc <= 0 || dialect != 0) { + syslog(LOG_ERR, "smbrdr: negotiate (response error)"); + smbrdr_handle_free(&srh); + return (-1); + } + sess->secmode = tmp_secmode; + sess->challenge_len = tmp_clen; + + rc = smb_msgbuf_decode(mb, "#c", + sess->challenge_len, sess->challenge_key); + if (rc <= 0) { + syslog(LOG_ERR, "smbrdr: negotiate (decode error)"); + smbrdr_handle_free(&srh); + return (-1); + } + + smbrdr_handle_free(&srh); + + if ((sess->secmode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRED) && + (sess->secmode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED)) { + sess->sign_ctx.ssc_flags |= SMB_SCF_REQUIRED; + syslog(LOG_DEBUG, "smbrdr: %s requires signing", + sess->di.server); + } + + sess->state = SDB_SSTATE_NEGOTIATED; + return (0); +} + +/* + * smbrdr_session_init + * + * Allocate an available slot in session table for the specified domain + * information. + * + * IMPORTANT! the returned session will be locked caller has to unlock + * it by calling smbrdr_session_unlock() after it's done with + * the pointer. + */ +static struct sdb_session * +smbrdr_session_init(smb_ntdomain_t *di) +{ + struct sdb_session *session = 0; + int i; + char *p; + + if (di == 0) + return (0); + + for (i = 0; i < MLSVC_DOMAIN_MAX; ++i) { + session = &session_table[i]; + + (void) rw_wrlock(&session->rwl); + if (session->state == SDB_SSTATE_START) { + smbrdr_session_clear(session); + bcopy(di, &session->di, sizeof (smb_ntdomain_t)); + (void) utf8_strupr(session->di.domain); + (void) utf8_strupr(session->di.server); + + smb_config_rdlock(); + p = smb_config_getstr(SMB_CI_NBSCOPE); + (void) strlcpy(session->scope, p, SMB_PI_MAX_SCOPE); + smb_config_unlock(); + + (void) strlcpy(session->native_os, + "Solaris", SMB_PI_MAX_NATIVE_OS); + (void) strlcpy(session->native_lanman, + "Windows NT 4.0", SMB_PI_MAX_LANMAN); + session->sock = -1; + session->port = smbrdr_ports[0]; + session->smb_flags = SMB_FLAGS_CANONICALIZED_PATHS + | SMB_FLAGS_CASE_INSENSITIVE; + + session->smb_flags2 = SMB_FLAGS2_KNOWS_LONG_NAMES + | SMB_FLAGS2_KNOWS_EAS; + + /* + * Note that by sending vc=0 server will shutdown all + * the other connections with NAS if there is any. + */ + session->vc = 0; + session->sid = ++session_id; + if (session->sid == 0) + session->sid = 1; + session->state = SDB_SSTATE_INIT; + return (session); + } + (void) rw_unlock(&session->rwl); + } + + syslog(LOG_WARNING, "smbrdr: no session available"); + return (0); +} + +/* + * smbrdr_session_disconnect + * + * This is the entry point for disconnecting an SMB connection. Ensure + * that all logons and shares associated with this session are + * terminated and then free the session. + * + * if 'cleanup' is 1 it means that only sessions that are not active + * should be cleaned up. if 'cleanup' is 0 disconnect the session in any + * states. + */ +static void +smbrdr_session_disconnect(struct sdb_session *session, int cleanup) +{ + int state; + + if (session == 0) { + syslog(LOG_ERR, "smbrdr: (disconnect) null session"); + return; + } + + state = session->state; + if ((state != SDB_SSTATE_DISCONNECTING) && + (state != SDB_SSTATE_CLEANING) && + (state != SDB_SSTATE_START)) { + if ((cleanup == 0) || (state == SDB_SSTATE_STALE)) { + /* + * if session is in stale state it means the connection + * is lost so no logoff, tdcon, or close can actually + * be sent, thus only cleanup our side. + */ + session->state = (state == SDB_SSTATE_STALE) + ? SDB_SSTATE_CLEANING : SDB_SSTATE_DISCONNECTING; + (void) smbrdr_smb_logoff(&session->logon); + nb_close(session->sock); + smbrdr_session_clear(session); + } + } +} + +/* + * smbrdr_session_unlock + * + * Unlock given session structure. + */ +void +smbrdr_session_unlock(struct sdb_session *session) +{ + if (session) + (void) rw_unlock(&session->rwl); +} + +/* + * smbrdr_session_lock + * + * Lookup the session associated with the specified domain controller. + * If a match is found, we return a pointer to the session, Otherwise + * we return null. Only sessions in "negotiated" state are checked. + * This mechanism is very simple and implies that we + * should only ever have one session open to any domain controller. + * + * IMPORTANT! the returned session will be locked caller has to unlock + * it by calling smbrdr_session_unlock() after it's done with + * the pointer. + */ +struct sdb_session * +smbrdr_session_lock(char *server, char *username, int lmode) +{ + struct sdb_session *session; + int i; + + if (server == 0) { + syslog(LOG_ERR, "smbrdr: (lookup) no server specified"); + return (0); + } + + for (i = 0; i < MLSVC_DOMAIN_MAX; ++i) { + session = &session_table[i]; + + (lmode == SDB_SLCK_READ) ? (void) rw_rdlock(&session->rwl) : + (void) rw_wrlock(&session->rwl); + + if ((session->state == SDB_SSTATE_NEGOTIATED) && + (strcasecmp(session->di.server, server) == 0)) { + if (username) { + if (strcasecmp(username, + session->logon.username) == 0) + return (session); + + (void) rw_unlock(&session->rwl); + return (0); + } + return (session); + } + + (void) rw_unlock(&session->rwl); + } + + return (0); +} + +/* + * mlsvc_session_native_values + * + * Given a file id (i.e. a named pipe fid), return the remote native + * OS and LM values for the associated session. + */ +int +mlsvc_session_native_values(int fid, int *remote_os, + int *remote_lm, int *pdc_type) +{ + struct sdb_session *session; + struct sdb_netuse *netuse; + struct sdb_ofile *ofile; + + if (remote_os == 0 || remote_lm == 0) { + syslog(LOG_ERR, "mlsvc_session_native_values: null"); + return (-1); + } + + if ((ofile = smbrdr_ofile_get(fid)) == 0) { + syslog(LOG_ERR, + "mlsvc_session_native_values: unknown file (%d)", fid); + return (-1); + } + + netuse = ofile->netuse; + session = netuse->session; + + *remote_os = session->remote_os; + *remote_lm = session->remote_lm; + if (pdc_type) + *pdc_type = session->pdc_type; + smbrdr_ofile_put(ofile); + return (0); +} + +/* + * smbrdr_disconnect_sessions + * + * Disconnects/cleanups all the sessions + */ +static void +smbrdr_disconnect_sessions(int cleanup) +{ + struct sdb_session *session; + int i; + + for (i = 0; i < MLSVC_DOMAIN_MAX; ++i) { + session = &session_table[i]; + (void) rw_wrlock(&session->rwl); + smbrdr_session_disconnect(&session_table[i], cleanup); + (void) rw_unlock(&session->rwl); + } +} + + +/* + * mlsvc_check_sessions + * + * This function should be run in an independent thread. At the time of + * writing it is called periodically from an infinite loop in the start + * up thread once initialization is complete. It sends a NetBIOS keep- + * alive message on each active session and handles cleanup if a session + * is closed from the remote end. Testing demonstrated that the domain + * controller will close a session after 15 minutes of inactivity. Note + * that neither NetBIOS keep-alive nor SMB echo is deemed as activity + * in this case, however, RPC requests appear to reset the timeout and + * keep the session open. Note that the NetBIOS request does stop the + * remote NetBIOS layer from timing out the connection. + */ +void +mlsvc_check_sessions(void) +{ + static int session_keep_alive; + struct sdb_session *session; + smb_ntdomain_t di; + int i; + + ++session_keep_alive; + + for (i = 0; i < MLSVC_DOMAIN_MAX; ++i) { + session = &session_table[i]; + + (void) rw_wrlock(&session->rwl); + + if (session->state < SDB_SSTATE_CONNECTED) { + (void) rw_unlock(&session->rwl); + continue; + } + + /* + * NetBIOS is only used on with port 139. The keep alive + * is not relevant over NetBIOS-less SMB over port 445. + * This is just to see if the socket is still alive. + */ + if (session->port == SSN_SRVC_TCP_PORT) { + if (nb_keep_alive(session->sock) != 0) { + session->state = SDB_SSTATE_STALE; + (void) rw_unlock(&session->rwl); + continue; + } + } + + if (session_keep_alive >= MLSVC_SESSION_FORCE_KEEPALIVE) { + if (smbrdr_smb_echo(session) != 0) { + syslog(LOG_WARNING, + "smbrdr: monitor[%s] cannot contact %s", + session->di.domain, session->di.server); + (void) memcpy(&di, &session->di, + sizeof (smb_ntdomain_t)); + session->state = SDB_SSTATE_STALE; + (void) rw_unlock(&session->rwl); + if (smb_getdomaininfo(0) == 0) + (void) smbrdr_locate_dc(di.domain); + } + } else + (void) rw_unlock(&session->rwl); + } + + if (session_keep_alive >= MLSVC_SESSION_FORCE_KEEPALIVE) { + session_keep_alive = 0; + /* cleanup */ + smbrdr_disconnect_sessions(1); + } +} + +/* + * smbrdr_dump_sessions + * + * Debug function to dump the session table. + */ +void +smbrdr_dump_sessions(void) +{ + struct sdb_session *session; + struct sdb_logon *logon; + char ipstr[16]; + int i; + + for (i = 0; i < MLSVC_DOMAIN_MAX; ++i) { + session = &session_table[i]; + + (void) rw_rdlock(&session->rwl); + if (session->state != SDB_SSTATE_START) { + (void) inet_ntop(AF_INET, + (const void *)(&session->di.ipaddr), + ipstr, sizeof (ipstr)); + + syslog(LOG_DEBUG, "session[%d]: state=%d", + i, session->state); + syslog(LOG_DEBUG, "session[%d]: %s %s (%s)", i, + session->di.domain, session->di.server, ipstr); + syslog(LOG_DEBUG, "session[%d]: %s %s (sock=%d)", i, + session->native_os, session->native_lanman, + session->sock); + + logon = &session->logon; + if (logon->type != SDB_LOGON_NONE) + syslog(LOG_DEBUG, "logon[%d]: %s (uid=%d)", + i, logon->username, logon->uid); + } + (void) rw_unlock(&session->rwl); + } +} + +/* + * mlsvc_echo + */ +int +mlsvc_echo(char *server) +{ + struct sdb_session *session; + int res = 0; + + if ((session = smbrdr_session_lock(server, 0, SDB_SLCK_WRITE)) == 0) + return (1); + + if (smbrdr_smb_echo(session) != 0) { + session->state = SDB_SSTATE_STALE; + res = -1; + } + + smbrdr_session_unlock(session); + return (res); +} + +/* + * smbrdr_smb_echo + * + * This request can be used to test the connection to the server. The + * server should echo the data sent. The server should ignore the tid + * in the header, so this request when there are no tree connections. + * See CIFS/1.0 section 4.1.7. + * + * Return 0 on success. Otherwise return a -ve error code. + */ +static int +smbrdr_smb_echo(struct sdb_session *session) +{ + static char *echo_str = "smbrdr"; + smbrdr_handle_t srh; + smb_hdr_t smb_hdr; + DWORD status; + int rc; + + if ((session->state == SDB_SSTATE_DISCONNECTING) || + (session->state == SDB_SSTATE_CLEANING) || + (session->state == SDB_SSTATE_STALE)) { + return (-1); + } + + status = smbrdr_request_init(&srh, SMB_COM_ECHO, session, 0, 0); + + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrEcho: %s", xlate_nt_status(status)); + return (-1); + } + + rc = smb_msgbuf_encode(&srh.srh_mbuf, "bwws", 1, 1, + strlen(echo_str), echo_str); + if (rc <= 0) { + syslog(LOG_ERR, "SmbrdrEcho: encode failed"); + smbrdr_handle_free(&srh); + return (-1); + } + + status = smbrdr_exchange(&srh, &smb_hdr, 10); + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrEcho: %s", xlate_nt_status(status)); + rc = -1; + } else { + rc = 0; + } + + smbrdr_handle_free(&srh); + return (rc); +} + +/* + * smbrdr_locate_dc + * + * Locate a domain controller. Note that this may close an existing + * connection to the current domain controller. + */ +static int +smbrdr_locate_dc(char *domain) +{ + if (mlsvc_locate_pdc) + return (mlsvc_locate_pdc(domain)); + + return (0); +} diff --git a/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_transact.c b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_transact.c new file mode 100644 index 0000000000..24bf3a5cea --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/common/smbrdr_transact.c @@ -0,0 +1,254 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * SMB transaction functions to support MLRPC. + */ + +#include <syslog.h> +#include <strings.h> + +#include <smbsrv/libsmbrdr.h> + +#include <smbsrv/ntstatus.h> +#include <smbsrv/smb.h> +#include <smbrdr.h> + +/* + * The pipe filename, length (including the null terminator) + * and the buffer size for the transaction. Moving to unicode + * revealed that the length should not include the null. + */ +#define TX_FILENAME "\\PIPE\\" +#define TX_FILENAME_ASCII_LEN 6 +#define TX_FILENAME_WCHAR_LEN 14 + + +static int prep_smb_transact(smb_msgbuf_t *, unsigned short, char *, + unsigned short, unsigned short, unsigned); +static int decode_smb_transact(smb_msgbuf_t *, char *, unsigned, + smb_transact_rsp_t *); + +/* + * smbrdr_rpc_transact + * + * Send a SMB_COM_TRANSACTION request. + */ +int +smbrdr_rpc_transact(int fid, char *out_buf, int out_len, + char *in_buf, int in_len) +{ + struct sdb_session *session; + struct sdb_netuse *netuse; + struct sdb_ofile *ofile; + struct sdb_logon *logon; + smb_transact_rsp_t rsp; + smbrdr_handle_t srh; + smb_msgbuf_t *mb; + DWORD status; + int rc; + unsigned short rcv_dcnt; + int cur_inlen; + int first_rsp; + + if ((ofile = smbrdr_ofile_get(fid)) == 0) + return (-1); + + netuse = ofile->netuse; + session = netuse->session; + logon = &session->logon; + + status = smbrdr_request_init(&srh, SMB_COM_TRANSACTION, + session, logon, netuse); + + if (status != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "SmbrdrTransact: %s", xlate_nt_status(status)); + smbrdr_ofile_put(ofile); + return (-1); + } + + mb = &srh.srh_mbuf; + + rc = prep_smb_transact(mb, ofile->fid, out_buf, out_len, in_len, + session->remote_caps & CAP_UNICODE); + if (rc < 0) { + syslog(LOG_ERR, + "smbrdr_rpc_transact: prep_smb_transact failed"); + smbrdr_handle_free(&srh); + smbrdr_ofile_put(ofile); + return (rc); + } + + smbrdr_lock_transport(); + + status = smbrdr_send(&srh); + if (status != NT_STATUS_SUCCESS) { + smbrdr_unlock_transport(); + smbrdr_handle_free(&srh); + smbrdr_ofile_put(ofile); + syslog(LOG_ERR, "smbrdr_rpc_transact: send failed"); + return (-1); + } + + rcv_dcnt = 0; + cur_inlen = in_len; + first_rsp = 1; + + do { + if (smbrdr_rcv(&srh, first_rsp) != NT_STATUS_SUCCESS) { + syslog(LOG_ERR, "smbrdr_rpc_transact: nb_rcv failed"); + rc = -1; + break; + } + + rc = decode_smb_transact(mb, in_buf, cur_inlen, &rsp); + if (rc < 0 || rsp.TotalDataCount > in_len) { + syslog(LOG_ERR, + "SmbTransact: transact decode failure!"); + rc = -1; + break; + } + + rcv_dcnt += rsp.DataCount; + cur_inlen -= rsp.DataCount; + first_rsp = 0; + + } while (rcv_dcnt < rsp.TotalDataCount); + + smbrdr_unlock_transport(); + smbrdr_handle_free(&srh); + smbrdr_ofile_put(ofile); + + return ((rc < 0) ? rc : rcv_dcnt); +} + + +/* + * prep_smb_transact + * + * Prepare the SMB_COM_TRANSACTION request. + */ +static int +prep_smb_transact(smb_msgbuf_t *mb, unsigned short fid, char *out, + unsigned short out_len, unsigned short in_max, unsigned unicode) +{ + int data_off; + int rc; + unsigned short bcc; + + /* + * The byte count seems to include the pad + * byte to word align the filename and two + * spurious pad bytes between the filename + * and the transaction data. + */ + bcc = out_len + 3; + bcc += (unicode) ? TX_FILENAME_WCHAR_LEN : TX_FILENAME_ASCII_LEN; + + data_off = 32; /* sizeof SMB header up to smb_wct */ + data_off += 1; /* sizeof smb_wct */ + data_off += 16*2; /* sizeof word parameters */ + data_off += 2; /* sizeof smb_bcc */ + data_off += (unicode) ? TX_FILENAME_WCHAR_LEN : TX_FILENAME_ASCII_LEN; + data_off += 3; + /* this is where data starts */ + + rc = smb_msgbuf_encode(mb, + "(wct)b" + "(tpscnt)w (tdscnt)w (mprcnt)w (mdrcnt)w (msrcnt)b" + "(rsvd). (flags)w (timeo)l (rsvd1)2." + "(pscnt)w (psoff)w (dscnt)w (dsoff)w (suwcnt)b" + "(rsvd2). (pipop)w (fid)w (bcc)w (fname)u", + 16, /* smb_wct */ + 0, /* total parm bytes */ + out_len, /* total data bytes */ + 0, /* max parm bytes to ret */ + in_max, /* max data bytes to ret */ + 0, /* max setup words to ret */ + 0, /* transact flags */ + 0, /* transact timeout */ + 0, /* parameter bytes */ + data_off, /* parameter offset */ + out_len, /* data bytes */ + data_off, /* data offset */ + 2, /* total setup words */ + 0x0026, /* OP=TransactNmPipe */ + fid, /* FID */ + bcc, /* byte count */ + TX_FILENAME); /* file name */ + + /* + * Transaction data - padded. + */ + rc = smb_msgbuf_encode(mb, "..#c", out_len, out); + return (rc); +} + + +/* + * decode_smb_transact + * + * Decode the response from the SMB_COM_TRANSACTION request. + */ +static int +decode_smb_transact(smb_msgbuf_t *mb, char *in, unsigned in_len, + smb_transact_rsp_t *rsp) +{ + int rc; + + rc = smb_msgbuf_decode(mb, "b", &rsp->WordCount); + if (rc <= 0 || rsp->WordCount < 10) { + syslog(LOG_ERR, "SmbTransact: invalid word count"); + return (-1); + } + + rc = smb_msgbuf_decode(mb, + "(tpscnt)w (tdscnt)w (rsvd)2." + "(pscnt)w (psoff)w (psdisp)w (dscnt)w (dsoff)w" + "(dsdisp)w (suwcnt)b (rsvd). (bcc)w", + &rsp->TotalParamCount, /* Total parm bytes */ + &rsp->TotalDataCount, /* Total data bytes */ + &rsp->ParamCount, /* Parm bytes this buffer */ + &rsp->ParamOffset, /* Parm offset from hdr */ + &rsp->ParamDisplacement, /* Parm displacement */ + &rsp->DataCount, /* Data bytes this buffer */ + &rsp->DataOffset, /* Data offset from hdr */ + &rsp->DataDisplacement, /* Data displacement */ + &rsp->SetupCount, /* Setup word count */ + &rsp->BCC); /* smb_bcc */ + + if (rc <= 0) + return (-1); + + if (rsp->DataCount > in_len) + return (-1); + + bcopy(mb->base + rsp->DataOffset, + in + rsp->DataDisplacement, rsp->DataCount); + + return (0); +} diff --git a/usr/src/lib/smbsrv/libsmbrdr/i386/Makefile b/usr/src/lib/smbsrv/libsmbrdr/i386/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/i386/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libsmbrdr/sparc/Makefile b/usr/src/lib/smbsrv/libsmbrdr/sparc/Makefile new file mode 100644 index 0000000000..f91f0270e9 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/sparc/Makefile @@ -0,0 +1,30 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/smbsrv/libsmbrdr/sparcv9/Makefile b/usr/src/lib/smbsrv/libsmbrdr/sparcv9/Makefile new file mode 100644 index 0000000000..a2f97019c8 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbrdr/sparcv9/Makefile @@ -0,0 +1,31 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) |
