/* libguestfs * Copyright (C) 2009-2011 Red Hat Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #define _BSD_SOURCE /* for mkdtemp, usleep */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_SYS_SOCKET_H #include #endif #ifdef HAVE_SYS_UN_H #include #endif #include #include #include "c-ctype.h" #include "ignore-value.h" #include "glthread/lock.h" #include "guestfs.h" #include "guestfs-internal.h" #include "guestfs-internal-actions.h" #include "guestfs_protocol.h" static int launch_appliance (guestfs_h *g); static int64_t timeval_diff (const struct timeval *x, const struct timeval *y); static int connect_unix_socket (guestfs_h *g, const char *sock); static int qemu_supports (guestfs_h *g, const char *option); #if 0 static int qemu_supports_re (guestfs_h *g, const pcre *option_regex); static void compile_regexps (void) __attribute__((constructor)); static void free_regexps (void) __attribute__((destructor)); static void compile_regexps (void) { const char *err; int offset; #define COMPILE(re,pattern,options) \ do { \ re = pcre_compile ((pattern), (options), &err, &offset, NULL); \ if (re == NULL) { \ ignore_value (write (2, err, strlen (err))); \ abort (); \ } \ } while (0) } static void free_regexps (void) { } #endif /* Add a string to the current command line. */ static void incr_cmdline_size (guestfs_h *g) { if (g->cmdline == NULL) { /* g->cmdline[0] is reserved for argv[0], set in guestfs_launch. */ g->cmdline_size = 1; g->cmdline = safe_malloc (g, sizeof (char *)); g->cmdline[0] = NULL; } g->cmdline_size++; g->cmdline = safe_realloc (g, g->cmdline, sizeof (char *) * g->cmdline_size); } static int add_cmdline (guestfs_h *g, const char *str) { if (g->state != CONFIG) { error (g, _("command line cannot be altered after qemu subprocess launched")); return -1; } incr_cmdline_size (g); g->cmdline[g->cmdline_size-1] = safe_strdup (g, str); return 0; } size_t guestfs___checkpoint_cmdline (guestfs_h *g) { return g->cmdline_size; } void guestfs___rollback_cmdline (guestfs_h *g, size_t pos) { size_t i; assert (g->cmdline_size >= pos); for (i = pos; i < g->cmdline_size; ++i) free (g->cmdline[i]); g->cmdline_size = pos; } /* Internal command to return the command line. */ char ** guestfs__debug_cmdline (guestfs_h *g) { size_t i; char **r; if (g->cmdline == NULL) { r = safe_malloc (g, sizeof (char *) * 1); r[0] = NULL; return r; } r = safe_malloc (g, sizeof (char *) * (g->cmdline_size + 1)); r[0] = safe_strdup (g, g->qemu); /* g->cmdline[0] is always NULL */ for (i = 1; i < g->cmdline_size; ++i) r[i] = safe_strdup (g, g->cmdline[i]); r[g->cmdline_size] = NULL; return r; /* caller frees */ } int guestfs__config (guestfs_h *g, const char *qemu_param, const char *qemu_value) { if (qemu_param[0] != '-') { error (g, _("guestfs_config: parameter must begin with '-' character")); return -1; } /* A bit fascist, but the user will probably break the extra * parameters that we add if they try to set any of these. */ if (STREQ (qemu_param, "-kernel") || STREQ (qemu_param, "-initrd") || STREQ (qemu_param, "-nographic") || STREQ (qemu_param, "-serial") || STREQ (qemu_param, "-full-screen") || STREQ (qemu_param, "-std-vga") || STREQ (qemu_param, "-vnc")) { error (g, _("guestfs_config: parameter '%s' isn't allowed"), qemu_param); return -1; } if (add_cmdline (g, qemu_param) != 0) return -1; if (qemu_value != NULL) { if (add_cmdline (g, qemu_value) != 0) return -1; } return 0; } /* cache=off improves reliability in the event of a host crash. * * However this option causes qemu to try to open the file with * O_DIRECT. This fails on some filesystem types (notably tmpfs). * So we check if we can open the file with or without O_DIRECT, * and use cache=off (or not) accordingly. */ static int test_cache_off (guestfs_h *g, const char *filename) { int fd = open (filename, O_RDONLY|O_DIRECT); if (fd >= 0) { close (fd); return 1; } fd = open (filename, O_RDONLY); if (fd >= 0) { close (fd); return 0; } perrorf (g, "%s", filename); return -1; } /* Check string parameter matches ^[-_[:alnum:]]+$ (in C locale). */ static int valid_format_iface (const char *str) { size_t len = strlen (str); if (len == 0) return 0; while (len > 0) { char c = *str++; len--; if (c != '-' && c != '_' && !c_isalnum (c)) return 0; } return 1; } int guestfs__add_drive_opts (guestfs_h *g, const char *filename, const struct guestfs_add_drive_opts_argv *optargs) { int readonly; const char *format; const char *iface; if (strchr (filename, ',') != NULL) { error (g, _("filename cannot contain ',' (comma) character")); return -1; } readonly = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK ? optargs->readonly : 0; format = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK ? optargs->format : NULL; iface = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK ? optargs->iface : DRIVE_IF; if (format && !valid_format_iface (format)) { error (g, _("%s parameter is empty or contains disallowed characters"), "format"); return -1; } if (!valid_format_iface (iface)) { error (g, _("%s parameter is empty or contains disallowed characters"), "iface"); return -1; } /* For writable files, see if we can use cache=off. This also * checks for the existence of the file. For readonly we have * to do the check explicitly. */ int use_cache_off = readonly ? 0 : test_cache_off (g, filename); if (use_cache_off == -1) return -1; if (readonly) { if (access (filename, F_OK) == -1) { perrorf (g, "%s", filename); return -1; } } /* Construct the final -drive parameter. */ size_t len = 64 + strlen (filename) + strlen (iface); if (format) len += strlen (format); char buf[len]; snprintf (buf, len, "file=%s%s%s%s%s,if=%s", filename, readonly ? ",snapshot=on" : "", use_cache_off ? ",cache=off" : "", format ? ",format=" : "", format ? format : "", iface); return guestfs__config (g, "-drive", buf); } int guestfs__add_drive (guestfs_h *g, const char *filename) { struct guestfs_add_drive_opts_argv optargs = { .bitmask = 0, }; return guestfs__add_drive_opts (g, filename, &optargs); } int guestfs__add_drive_ro (guestfs_h *g, const char *filename) { struct guestfs_add_drive_opts_argv optargs = { .bitmask = GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK, .readonly = 1, }; return guestfs__add_drive_opts (g, filename, &optargs); } int guestfs__add_drive_with_if (guestfs_h *g, const char *filename, const char *iface) { struct guestfs_add_drive_opts_argv optargs = { .bitmask = GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK, .iface = iface, }; return guestfs__add_drive_opts (g, filename, &optargs); } int guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename, const char *iface) { struct guestfs_add_drive_opts_argv optargs = { .bitmask = GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK | GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK, .iface = iface, .readonly = 1, }; return guestfs__add_drive_opts (g, filename, &optargs); } int guestfs__add_cdrom (guestfs_h *g, const char *filename) { if (strchr (filename, ',') != NULL) { error (g, _("filename cannot contain ',' (comma) character")); return -1; } if (access (filename, F_OK) == -1) { perrorf (g, "%s", filename); return -1; } return guestfs__config (g, "-cdrom", filename); } static int is_openable (guestfs_h *g, const char *path, int flags); int guestfs__launch (guestfs_h *g) { /* Configured? */ if (g->state != CONFIG) { error (g, _("the libguestfs handle has already been launched")); return -1; } /* Make the temporary directory. */ if (!g->tmpdir) { TMP_TEMPLATE_ON_STACK (dir_template); g->tmpdir = safe_strdup (g, dir_template); if (mkdtemp (g->tmpdir) == NULL) { perrorf (g, _("%s: cannot create temporary directory"), dir_template); return -1; } } /* Allow anyone to read the temporary directory. The socket in this * directory won't be readable but anyone can see it exists if they * want. (RHBZ#610880). */ if (chmod (g->tmpdir, 0755) == -1) warning (g, "chmod: %s: %m (ignored)", g->tmpdir); /* Launch the appliance or attach to an existing daemon. */ switch (g->attach_method) { case ATTACH_METHOD_APPLIANCE: return launch_appliance (g); case ATTACH_METHOD_UNIX: return connect_unix_socket (g, g->attach_method_arg); default: abort (); } } static int launch_appliance (guestfs_h *g) { int r; int wfd[2], rfd[2]; char guestfsd_sock[256]; struct sockaddr_un addr; /* At present you must add drives before starting the appliance. In * future when we enable hotplugging you won't need to do this. */ if (!g->cmdline) { error (g, _("you must call guestfs_add_drive before guestfs_launch")); return -1; } /* Start the clock ... */ gettimeofday (&g->launch_t, NULL); guestfs___launch_send_progress (g, 0); /* Locate and/or build the appliance. */ char *kernel = NULL, *initrd = NULL, *appliance = NULL; if (guestfs___build_appliance (g, &kernel, &initrd, &appliance) == -1) return -1; guestfs___launch_send_progress (g, 3); if (g->verbose) guestfs___print_timestamped_message (g, "begin testing qemu features"); /* Get qemu help text and version. */ if (qemu_supports (g, NULL) == -1) goto cleanup0; /* Using virtio-serial, we need to create a local Unix domain socket * for qemu to connect to. */ snprintf (guestfsd_sock, sizeof guestfsd_sock, "%s/guestfsd.sock", g->tmpdir); unlink (guestfsd_sock); g->sock = socket (AF_UNIX, SOCK_STREAM, 0); if (g->sock == -1) { perrorf (g, "socket"); goto cleanup0; } if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { perrorf (g, "fcntl"); goto cleanup0; } addr.sun_family = AF_UNIX; strncpy (addr.sun_path, guestfsd_sock, UNIX_PATH_MAX); addr.sun_path[UNIX_PATH_MAX-1] = '\0'; if (bind (g->sock, &addr, sizeof addr) == -1) { perrorf (g, "bind"); goto cleanup0; } if (listen (g->sock, 1) == -1) { perrorf (g, "listen"); goto cleanup0; } if (!g->direct) { if (pipe (wfd) == -1 || pipe (rfd) == -1) { perrorf (g, "pipe"); goto cleanup0; } } if (g->verbose) guestfs___print_timestamped_message (g, "finished testing qemu features"); r = fork (); if (r == -1) { perrorf (g, "fork"); if (!g->direct) { close (wfd[0]); close (wfd[1]); close (rfd[0]); close (rfd[1]); } goto cleanup0; } if (r == 0) { /* Child (qemu). */ char buf[256]; /* Set up the full command line. Do this in the subprocess so we * don't need to worry about cleaning up. */ g->cmdline[0] = g->qemu; if (qemu_supports (g, "-nodefconfig")) add_cmdline (g, "-nodefconfig"); /* The qemu -machine option (added 2010-12) is a bit more sane * since it falls back through various different acceleration * modes, so try that first (thanks Markus Armbruster). */ if (qemu_supports (g, "-machine")) { add_cmdline (g, "-machine"); add_cmdline (g, "accel=kvm:tcg"); } else { /* qemu sometimes needs this option to enable hardware * virtualization, but some versions of 'qemu-kvm' will use KVM * regardless (even where this option appears in the help text). * It is rumoured that there are versions of qemu where supplying * this option when hardware virtualization is not available will * cause qemu to fail, so we we have to check at least that * /dev/kvm is openable. That's not reliable, since /dev/kvm * might be openable by qemu but not by us (think: SELinux) in * which case the user would not get hardware virtualization, * although at least shouldn't fail. A giant clusterfuck with the * qemu command line, again. */ if (qemu_supports (g, "-enable-kvm") && is_openable (g, "/dev/kvm", O_RDWR)) add_cmdline (g, "-enable-kvm"); } /* Newer versions of qemu (from around 2009/12) changed the * behaviour of monitors so that an implicit '-monitor stdio' is * assumed if we are in -nographic mode and there is no other * -monitor option. Only a single stdio device is allowed, so * this broke the '-serial stdio' option. There is a new flag * called -nodefaults which gets rid of all this default crud, so * let's use that to avoid this and any future surprises. */ if (qemu_supports (g, "-nodefaults")) add_cmdline (g, "-nodefaults"); add_cmdline (g, "-nographic"); snprintf (buf, sizeof buf, "%d", g->memsize); add_cmdline (g, "-m"); add_cmdline (g, buf); /* Force exit instead of reboot on panic */ add_cmdline (g, "-no-reboot"); /* These options recommended by KVM developers to improve reliability. */ if (qemu_supports (g, "-no-hpet")) add_cmdline (g, "-no-hpet"); if (qemu_supports (g, "-rtc-td-hack")) add_cmdline (g, "-rtc-td-hack"); /* Create the virtio serial bus. */ add_cmdline (g, "-device"); add_cmdline (g, "virtio-serial"); #if 0 /* Use virtio-console (a variant form of virtio-serial) for the * guest's serial console. */ add_cmdline (g, "-chardev"); add_cmdline (g, "stdio,id=console"); add_cmdline (g, "-device"); add_cmdline (g, "virtconsole,chardev=console,name=org.libguestfs.console.0"); #else /* When the above works ... until then: */ add_cmdline (g, "-serial"); add_cmdline (g, "stdio"); #endif /* Set up virtio-serial for the communications channel. */ add_cmdline (g, "-chardev"); snprintf (buf, sizeof buf, "socket,path=%s,id=channel0", guestfsd_sock); add_cmdline (g, buf); add_cmdline (g, "-device"); add_cmdline (g, "virtserialport,chardev=channel0,name=org.libguestfs.channel.0"); /* Enable user networking. */ if (g->enable_network) { add_cmdline (g, "-netdev"); add_cmdline (g, "user,id=usernet,net=169.254.0.0/16"); add_cmdline (g, "-device"); add_cmdline (g, NET_IF ",netdev=usernet"); } #define LINUX_CMDLINE \ "panic=1 " /* force kernel to panic if daemon exits */ \ "console=ttyS0 " /* serial console */ \ "udevtimeout=300 " /* good for very slow systems (RHBZ#480319) */ \ "noapic " /* workaround for RHBZ#502058 - ok if not SMP */ \ "acpi=off " /* we don't need ACPI, turn it off */ \ "printk.time=1 " /* display timestamp before kernel messages */ \ "cgroup_disable=memory " /* saves us about 5 MB of RAM */ /* Linux kernel command line. */ snprintf (buf, sizeof buf, LINUX_CMDLINE "%s " /* (selinux) */ "%s " /* (verbose) */ "TERM=%s " /* (TERM environment variable) */ "%s", /* (append) */ g->selinux ? "selinux=1 enforcing=0" : "selinux=0", g->verbose ? "guestfs_verbose=1" : "", getenv ("TERM") ? : "linux", g->append ? g->append : ""); add_cmdline (g, "-kernel"); add_cmdline (g, kernel); add_cmdline (g, "-initrd"); add_cmdline (g, initrd); add_cmdline (g, "-append"); add_cmdline (g, buf); /* Add the ext2 appliance drive (last of all). */ if (appliance) { const char *cachemode = ""; if (qemu_supports (g, "cache=")) { if (qemu_supports (g, "unsafe")) cachemode = ",cache=unsafe"; else if (qemu_supports (g, "writeback")) cachemode = ",cache=writeback"; } char buf2[PATH_MAX + 64]; add_cmdline (g, "-drive"); snprintf (buf2, sizeof buf2, "file=%s,snapshot=on,if=" DRIVE_IF "%s", appliance, cachemode); add_cmdline (g, buf2); } /* Finish off the command line. */ incr_cmdline_size (g); g->cmdline[g->cmdline_size-1] = NULL; if (g->verbose) guestfs___print_timestamped_argv (g, (const char **)g->cmdline); if (!g->direct) { /* Set up stdin, stdout, stderr. */ close (0); close (1); close (wfd[1]); close (rfd[0]); /* Stdin. */ if (dup (wfd[0]) == -1) { dup_failed: perror ("dup failed"); _exit (EXIT_FAILURE); } /* Stdout. */ if (dup (rfd[1]) == -1) goto dup_failed; /* Particularly since qemu 0.15, qemu spews all sorts of debug * information on stderr. It is useful to both capture this and * not confuse casual users, so send stderr to the pipe as well. */ close (2); if (dup (rfd[1]) == -1) goto dup_failed; close (wfd[0]); close (rfd[1]); } /* Put qemu in a new process group. */ if (g->pgroup) setpgid (0, 0); setenv ("LC_ALL", "C", 1); execv (g->qemu, g->cmdline); /* Run qemu. */ perror (g->qemu); _exit (EXIT_FAILURE); } /* Parent (library). */ g->pid = r; free (kernel); kernel = NULL; free (initrd); initrd = NULL; free (appliance); appliance = NULL; /* Fork the recovery process off which will kill qemu if the parent * process fails to do so (eg. if the parent segfaults). */ g->recoverypid = -1; if (g->recovery_proc) { r = fork (); if (r == 0) { pid_t qemu_pid = g->pid; pid_t parent_pid = getppid (); /* It would be nice to be able to put this in the same process * group as qemu (ie. setpgid (0, qemu_pid)). However this is * not possible because we don't have any guarantee here that * the qemu process has started yet. */ if (g->pgroup) setpgid (0, 0); /* Writing to argv is hideously complicated and error prone. See: * http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/misc/ps_status.c;hb=HEAD */ /* Loop around waiting for one or both of the other processes to * disappear. It's fair to say this is very hairy. The PIDs that * we are looking at might be reused by another process. We are * effectively polling. Is the cure worse than the disease? */ for (;;) { if (kill (qemu_pid, 0) == -1) /* qemu's gone away, we aren't needed */ _exit (EXIT_SUCCESS); if (kill (parent_pid, 0) == -1) { /* Parent's gone away, qemu still around, so kill qemu. */ kill (qemu_pid, 9); _exit (EXIT_SUCCESS); } sleep (2); } } /* Don't worry, if the fork failed, this will be -1. The recovery * process isn't essential. */ g->recoverypid = r; } if (!g->direct) { /* Close the other ends of the pipe. */ close (wfd[0]); close (rfd[1]); if (fcntl (wfd[1], F_SETFL, O_NONBLOCK) == -1 || fcntl (rfd[0], F_SETFL, O_NONBLOCK) == -1) { perrorf (g, "fcntl"); goto cleanup1; } g->fd[0] = wfd[1]; /* stdin of child */ g->fd[1] = rfd[0]; /* stdout of child */ } else { g->fd[0] = open ("/dev/null", O_RDWR); if (g->fd[0] == -1) { perrorf (g, "open /dev/null"); goto cleanup1; } g->fd[1] = dup (g->fd[0]); if (g->fd[1] == -1) { perrorf (g, "dup"); close (g->fd[0]); goto cleanup1; } } g->state = LAUNCHING; /* Wait for qemu to start and to connect back to us via * virtio-serial and send the GUESTFS_LAUNCH_FLAG message. */ r = guestfs___accept_from_daemon (g); if (r == -1) goto cleanup1; close (g->sock); /* Close the listening socket. */ g->sock = r; /* This is the accepted data socket. */ if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { perrorf (g, "fcntl"); goto cleanup1; } uint32_t size; void *buf = NULL; r = guestfs___recv_from_daemon (g, &size, &buf); free (buf); if (r == -1) return -1; if (size != GUESTFS_LAUNCH_FLAG) { error (g, _("guestfs_launch failed, see earlier error messages")); goto cleanup1; } if (g->verbose) guestfs___print_timestamped_message (g, "appliance is up"); /* This is possible in some really strange situations, such as * guestfsd starts up OK but then qemu immediately exits. Check for * it because the caller is probably expecting to be able to send * commands after this function returns. */ if (g->state != READY) { error (g, _("qemu launched and contacted daemon, but state != READY")); goto cleanup1; } guestfs___launch_send_progress (g, 12); return 0; cleanup1: if (!g->direct) { close (wfd[1]); close (rfd[0]); } if (g->pid > 0) kill (g->pid, 9); if (g->recoverypid > 0) kill (g->recoverypid, 9); if (g->pid > 0) waitpid (g->pid, NULL, 0); if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0); g->fd[0] = -1; g->fd[1] = -1; g->pid = 0; g->recoverypid = 0; memset (&g->launch_t, 0, sizeof g->launch_t); cleanup0: if (g->sock >= 0) { close (g->sock); g->sock = -1; } g->state = CONFIG; free (kernel); free (initrd); free (appliance); return -1; } /* Alternate attach method: instead of launching the appliance, * connect to an existing unix socket. */ static int connect_unix_socket (guestfs_h *g, const char *sockpath) { int r; struct sockaddr_un addr; /* Start the clock ... */ gettimeofday (&g->launch_t, NULL); /* Set these to nothing so we don't try to kill random processes or * read from random file descriptors. */ g->pid = 0; g->recoverypid = 0; g->fd[0] = -1; g->fd[1] = -1; if (g->verbose) guestfs___print_timestamped_message (g, "connecting to %s", sockpath); g->sock = socket (AF_UNIX, SOCK_STREAM, 0); if (g->sock == -1) { perrorf (g, "socket"); return -1; } addr.sun_family = AF_UNIX; strncpy (addr.sun_path, sockpath, UNIX_PATH_MAX); addr.sun_path[UNIX_PATH_MAX-1] = '\0'; g->state = LAUNCHING; if (connect (g->sock, &addr, sizeof addr) == -1) { perrorf (g, "bind"); goto cleanup; } if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { perrorf (g, "fcntl"); goto cleanup; } uint32_t size; void *buf = NULL; r = guestfs___recv_from_daemon (g, &size, &buf); free (buf); if (r == -1) return -1; if (size != GUESTFS_LAUNCH_FLAG) { error (g, _("guestfs_launch failed, unexpected initial message from guestfsd")); goto cleanup; } if (g->verbose) guestfs___print_timestamped_message (g, "connected"); if (g->state != READY) { error (g, _("contacted guestfsd, but state != READY")); goto cleanup; } return 0; cleanup: close (g->sock); return -1; } /* launch (of the ordinary appliance) generates approximate progress * messages. Currently these are defined as follows: * * 0 / 12: launch clock starts * 3 / 12: appliance created * 6 / 12: detected that guest kernel started * 9 / 12: detected that /init script is running * 12 / 12: launch completed successfully * * Notes: * (1) This is not a documented ABI and the behaviour may be changed * or removed in future. * (2) Messages are only sent if more than 5 seconds has elapsed * since the launch clock started. * (3) There is a gross hack in proto.c to make this work. */ void guestfs___launch_send_progress (guestfs_h *g, int perdozen) { struct timeval tv; gettimeofday (&tv, NULL); if (timeval_diff (&g->launch_t, &tv) >= 5000) { guestfs_progress progress_message = { .proc = 0, .serial = 0, .position = perdozen, .total = 12 }; guestfs___progress_message_callback (g, &progress_message); } } /* Return the location of the tmpdir (eg. "/tmp") and allow users * to override it at runtime using $TMPDIR. * http://www.pathname.com/fhs/pub/fhs-2.3.html#TMPTEMPORARYFILES */ const char * guestfs_tmpdir (void) { const char *tmpdir; #ifdef P_tmpdir tmpdir = P_tmpdir; #else tmpdir = "/tmp"; #endif const char *t = getenv ("TMPDIR"); if (t) tmpdir = t; return tmpdir; } /* Return the location of the persistent tmpdir (eg. "/var/tmp") and * allow users to override it at runtime using $TMPDIR. * http://www.pathname.com/fhs/pub/fhs-2.3.html#VARTMPTEMPORARYFILESPRESERVEDBETWEE */ const char * guestfs___persistent_tmpdir (void) { const char *tmpdir; tmpdir = "/var/tmp"; const char *t = getenv ("TMPDIR"); if (t) tmpdir = t; return tmpdir; } /* Compute Y - X and return the result in milliseconds. * Approximately the same as this code: * http://www.mpp.mpg.de/~huber/util/timevaldiff.c */ static int64_t timeval_diff (const struct timeval *x, const struct timeval *y) { int64_t msec; msec = (y->tv_sec - x->tv_sec) * 1000; msec += (y->tv_usec - x->tv_usec) / 1000; return msec; } void guestfs___print_timestamped_argv (guestfs_h *g, const char * argv[]) { int i = 0; int needs_quote; char *buf = NULL; size_t len; FILE *fp; fp = open_memstream (&buf, &len); if (fp == NULL) { warning (g, "open_memstream: %m"); return; } struct timeval tv; gettimeofday (&tv, NULL); fprintf (fp, "[%05" PRIi64 "ms] ", timeval_diff (&g->launch_t, &tv)); while (argv[i]) { if (argv[i][0] == '-') /* -option starts a new line */ fprintf (fp, " \\\n "); if (i > 0) fputc (' ', fp); /* Does it need shell quoting? This only deals with simple cases. */ needs_quote = strcspn (argv[i], " ") != strlen (argv[i]); if (needs_quote) fputc ('\'', fp); fprintf (fp, "%s", argv[i]); if (needs_quote) fputc ('\'', fp); i++; } fclose (fp); debug (g, "%s", buf); free (buf); } void guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...) { va_list args; char *msg; int err; struct timeval tv; va_start (args, fs); err = vasprintf (&msg, fs, args); va_end (args); if (err < 0) return; gettimeofday (&tv, NULL); debug (g, "[%05" PRIi64 "ms] %s", timeval_diff (&g->launch_t, &tv), msg); free (msg); } static int read_all (guestfs_h *g, FILE *fp, char **ret); /* Test qemu binary (or wrapper) runs, and do 'qemu -help' and * 'qemu -version' so we know what options this qemu supports and * the version. */ static int test_qemu (guestfs_h *g) { char cmd[1024]; FILE *fp; snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -nographic -help", g->qemu); fp = popen (cmd, "r"); /* qemu -help should always work (qemu -version OTOH wasn't * supported by qemu 0.9). If this command doesn't work then it * probably indicates that the qemu binary is missing. */ if (!fp) { /* XXX This error is never printed, even if the qemu binary * doesn't exist. Why? */ error: perrorf (g, _("%s: command failed: If qemu is located on a non-standard path, try setting the LIBGUESTFS_QEMU environment variable."), cmd); return -1; } if (read_all (g, fp, &g->qemu_help) == -1) goto error; if (pclose (fp) == -1) goto error; snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -nographic -version 2>/dev/null", g->qemu); fp = popen (cmd, "r"); if (fp) { /* Intentionally ignore errors. */ read_all (g, fp, &g->qemu_version); pclose (fp); } return 0; } static int read_all (guestfs_h *g, FILE *fp, char **ret) { int r, n = 0; char *p; again: if (feof (fp)) { *ret = safe_realloc (g, *ret, n + 1); (*ret)[n] = '\0'; return n; } *ret = safe_realloc (g, *ret, n + BUFSIZ); p = &(*ret)[n]; r = fread (p, 1, BUFSIZ, fp); if (ferror (fp)) { perrorf (g, "read"); return -1; } n += r; goto again; } /* Test if option is supported by qemu command line (just by grepping * the help text). * * The first time this is used, it has to run the external qemu * binary. If that fails, it returns -1. * * To just do the first-time run of the qemu binary, call this with * option == NULL, in which case it will return -1 if there was an * error doing that. */ static int qemu_supports (guestfs_h *g, const char *option) { if (!g->qemu_help) { if (test_qemu (g) == -1) return -1; } if (option == NULL) return 1; return strstr (g->qemu_help, option) != NULL; } #if 0 /* As above but using a regex instead of a fixed string. */ static int qemu_supports_re (guestfs_h *g, const pcre *option_regex) { if (!g->qemu_help) { if (test_qemu (g) == -1) return -1; } return match (g, g->qemu_help, option_regex); } #endif /* Check if a file can be opened. */ static int is_openable (guestfs_h *g, const char *path, int flags) { int fd = open (path, flags); if (fd == -1) { debug (g, "is_openable: %s: %m", path); return 0; } close (fd); return 1; } /* You had to call this function after launch in versions <= 1.0.70, * but it is now a no-op. */ int guestfs__wait_ready (guestfs_h *g) { if (g->state != READY) { error (g, _("qemu has not been launched yet")); return -1; } return 0; } int guestfs__kill_subprocess (guestfs_h *g) { if (g->state == CONFIG) { error (g, _("no subprocess to kill")); return -1; } debug (g, "sending SIGTERM to process %d", g->pid); if (g->pid > 0) kill (g->pid, SIGTERM); if (g->recoverypid > 0) kill (g->recoverypid, 9); return 0; } /* Access current state. */ int guestfs__is_config (guestfs_h *g) { return g->state == CONFIG; } int guestfs__is_launching (guestfs_h *g) { return g->state == LAUNCHING; } int guestfs__is_ready (guestfs_h *g) { return g->state == READY; } int guestfs__is_busy (guestfs_h *g) { return g->state == BUSY; } int guestfs__get_state (guestfs_h *g) { return g->state; } 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954
#define OLD_NTDOMAIN 1

/* 
 *  Unix SMB/Netbios implementation.
 *  Version 1.9.
 *  RPC Pipe client / server routines
 *  Copyright (C) Andrew Tridgell              1992-1997,
 *  Copyright (C) Luke Kenneth Casson Leighton 1996-1997,
 *  Copyright (C) Paul Ashton                       1997.
 *  Copyright (C) Hewlett-Packard Company           1999.
 *  
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "includes.h"

extern int DEBUGLEVEL;

extern fstring global_myworkgroup;
extern pstring global_myname;
extern DOM_SID global_sam_sid;

extern rid_name domain_group_rids[];
extern rid_name domain_alias_rids[];
extern rid_name builtin_alias_rids[];

/*******************************************************************
  This next function should be replaced with something that
  dynamically returns the correct user info..... JRA.
 ********************************************************************/

static BOOL get_sampwd_entries(SAM_USER_INFO_21 *pw_buf,
				int start_idx,
                                int *total_entries, int *num_entries,
                                int max_num_entries,
                                uint16 acb_mask)
{
	void *vp = NULL;
	struct sam_passwd *pwd = NULL;

	(*num_entries) = 0;
	(*total_entries) = 0;

	if (pw_buf == NULL) return False;

	vp = startsmbpwent(False);
	if (!vp) {
		DEBUG(0, ("get_sampwd_entries: Unable to open SMB password database.\n"));
		return False;
	}

	while (((pwd = getsam21pwent(vp)) != NULL) && (*num_entries) < max_num_entries) {
		int user_name_len;

		if (start_idx > 0) {
			/* skip the requested number of entries.
			   not very efficient, but hey...
			 */
			start_idx--;
			continue;
		}

		user_name_len = strlen(pwd->smb_name);
		init_unistr2(&(pw_buf[(*num_entries)].uni_user_name), pwd->smb_name, user_name_len);
		init_uni_hdr(&(pw_buf[(*num_entries)].hdr_user_name), user_name_len);
		pw_buf[(*num_entries)].user_rid = pwd->user_rid;
		memset((char *)pw_buf[(*num_entries)].nt_pwd, '\0', 16);

		/* Now check if the NT compatible password is available. */
		if (pwd->smb_nt_passwd != NULL) {
			memcpy( pw_buf[(*num_entries)].nt_pwd , pwd->smb_nt_passwd, 16);
		}

		pw_buf[(*num_entries)].acb_info = (uint16)pwd->acct_ctrl;

		DEBUG(5, ("entry idx: %d user %s, rid 0x%x, acb %x",
		          (*num_entries), pwd->smb_name,
		          pwd->user_rid, pwd->acct_ctrl));

		if (acb_mask == 0 || IS_BITS_SET_SOME(pwd->acct_ctrl, acb_mask)) {
			DEBUG(5,(" acb_mask %x accepts\n", acb_mask));
			(*num_entries)++;
		} else {
			DEBUG(5,(" acb_mask %x rejects\n", acb_mask));
		}

		(*total_entries)++;
	}

	endsmbpwent(vp);

	return (*num_entries) > 0;
}

/*******************************************************************
 This function uses the username map file and tries to map a UNIX
 user name to an DOS name.  (Sort of the reverse of the
 map_username() function.)  Since more than one DOS name can map
 to the UNIX name, to reverse the mapping you have to specify
 which corresponding DOS name you want; that's where the name_idx
 parameter comes in.  Returns the string requested or NULL if it
 fails or can't complete the request for any reason.  This doesn't
 handle group names (starting with '@') or names starting with
 '+' or '&'.  If they are encountered, they are skipped.
********************************************************************/

static char *unmap_unixname(char *unix_user_name, int name_idx)
{
	char *mapfile = lp_username_map();
	char **lines;
	static pstring tok;
	int i;

	if (!*unix_user_name) return NULL;
	if (!*mapfile) return NULL;

	lines = file_lines_load(mapfile, NULL);
	if (!lines) {
		DEBUG(0,("unmap_unixname: can't open username map %s\n", mapfile));
		return NULL;
	}

	DEBUG(5,("unmap_unixname: scanning username map %s, index: %d\n", mapfile, name_idx));

	for (i=0; lines[i]; i++) {
		char *unixname = lines[i];
		char *dosname = strchr(unixname,'=');

		if (!dosname)
			continue;

		*dosname++ = 0;

		while (isspace(*unixname))
			unixname++;
		if ('!' == *unixname) {
			unixname++;
			while (*unixname && isspace(*unixname))
				unixname++;
		}
    
		if (!*unixname || strchr("#;",*unixname))
			continue;

		if (strncmp(unixname, unix_user_name, strlen(unix_user_name)))
			continue;

		/* We have matched the UNIX user name */

		while(next_token(&dosname, tok, LIST_SEP, sizeof(tok))) {
			if (!strchr("@&+", *tok)) {
				name_idx--;
				if (name_idx < 0 ) {
					break;
				}
			}
		}

		if (name_idx >= 0) {
			DEBUG(0,("unmap_unixname: index too high - not that many DOS names\n"));
			file_lines_free(lines);
			return NULL;
		} else {
			file_lines_free(lines);
			return tok;
		}
	}

	DEBUG(0,("unmap_unixname: Couldn't find the UNIX user name\n"));
	file_lines_free(lines);
	return NULL;
}

/*******************************************************************
 This function sets up a list of users taken from the list of
 users that UNIX knows about, as well as all the user names that
 Samba maps to a valid UNIX user name.  (This should work with
 /etc/passwd or NIS.)
********************************************************************/

static BOOL get_passwd_entries(SAM_USER_INFO_21 *pw_buf,
				int start_idx,
				int *total_entries, int *num_entries,
				int max_num_entries,
				uint16 acb_mask)
{
	static struct passwd *pwd = NULL;
	static uint32 pw_rid;
	static BOOL orig_done = False;
	static int current_idx = 0;
	static int mapped_idx = 0;

	DEBUG(5, ("get_passwd_entries: retrieving a list of UNIX users\n"));

	(*num_entries) = 0;
	(*total_entries) = 0;

	if (pw_buf == NULL) return False;

	if (current_idx == 0) {
		setpwent();
	}

	/* These two cases are inefficient, but should be called very rarely */
	/* they are the cases where the starting index isn't picking up      */
	/* where we left off last time.  It is efficient when it starts over */
	/* at zero though.                                                   */
	if (start_idx > current_idx) {
		/* We aren't far enough; advance to start_idx */
		while (current_idx < start_idx) {
			char *unmap_name;

			if(!orig_done) {
				if ((pwd = getpwent()) == NULL) break;
				current_idx++;
				orig_done = True;
			}

			while (((unmap_name = unmap_unixname(pwd->pw_name, mapped_idx)) != NULL) && 
			        (current_idx < start_idx)) {
				current_idx++;
				mapped_idx++;
			}

			if (unmap_name == NULL) {
				orig_done = False;
				mapped_idx = 0;
			}
		}
	} else if (start_idx < current_idx) {
		/* We are already too far; start over and advance to start_idx */
		endpwent();
		setpwent();
		current_idx = 0;
		mapped_idx = 0;
		orig_done = False;
		while (current_idx < start_idx) {
			char *unmap_name;

			if(!orig_done) {
				if ((pwd = getpwent()) == NULL) break;
				current_idx++;
				orig_done = True;
			}

			while (((unmap_name = unmap_unixname(pwd->pw_name, mapped_idx)) != NULL) && 
			        (current_idx < start_idx)) {
				current_idx++;
				mapped_idx++;
			}

			if (unmap_name == NULL) {
				orig_done = False;
				mapped_idx = 0;
			}
		}
	}

	/* now current_idx == start_idx */
	while ((*num_entries) < max_num_entries) {
		int user_name_len;
		char *unmap_name;

		/* This does the original UNIX user itself */
		if(!orig_done) {
			if ((pwd = getpwent()) == NULL) break;
			user_name_len = strlen(pwd->pw_name);
			pw_rid = pdb_uid_to_user_rid(pwd->pw_uid);
			init_unistr2(&(pw_buf[(*num_entries)].uni_user_name), pwd->pw_name, user_name_len);
			init_uni_hdr(&(pw_buf[(*num_entries)].hdr_user_name), user_name_len);
			pw_buf[(*num_entries)].user_rid = pw_rid;
			memset((char *)pw_buf[(*num_entries)].nt_pwd, '\0', 16);

			pw_buf[(*num_entries)].acb_info = ACB_NORMAL;

			DEBUG(5, ("get_passwd_entries: entry idx %d user %s, rid 0x%x\n", (*num_entries), pwd->pw_name, pw_rid));

			(*num_entries)++;
			(*total_entries)++;
			current_idx++;
			orig_done = True;
		}

		/* This does all the user names that map to the UNIX user */
		while (((unmap_name = unmap_unixname(pwd->pw_name, mapped_idx)) != NULL) && 
		        (*num_entries < max_num_entries)) {
			user_name_len = strlen(unmap_name);
			init_unistr2(&(pw_buf[(*num_entries)].uni_user_name), unmap_name, user_name_len);
			init_uni_hdr(&(pw_buf[(*num_entries)].hdr_user_name), user_name_len);
			pw_buf[(*num_entries)].user_rid = pw_rid;
			memset((char *)pw_buf[(*num_entries)].nt_pwd, '\0', 16);

			pw_buf[(*num_entries)].acb_info = ACB_NORMAL;

			DEBUG(5, ("get_passwd_entries: entry idx %d user %s, rid 0x%x\n", (*num_entries), pwd->pw_name, pw_rid));

			(*num_entries)++;
			(*total_entries)++;
			current_idx++;
			mapped_idx++;
		}

		if (unmap_name == NULL) {
			/* done with 'aliases', go on to next UNIX user */
			orig_done = False;
			mapped_idx = 0;
		}
	}

	if (pwd == NULL) {
		/* totally done, reset everything */
		endpwent();
		current_idx = 0;
		mapped_idx = 0;
	}

	return (*num_entries) > 0;
}

/*******************************************************************
 samr_reply_unknown_1
 ********************************************************************/
static void samr_reply_close_hnd(SAMR_Q_CLOSE_HND *q_u,
				prs_struct *rdata)
{
	SAMR_R_CLOSE_HND r_u;

	/* set up the SAMR unknown_1 response */
	memset((char *)r_u.pol.data, '\0', POL_HND_SIZE);

	/* close the policy handle */
	if (close_lsa_policy_hnd(&(q_u->pol)))
	{
		r_u.status = 0;
	}
	else
	{
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_INVALID;
	}

	DEBUG(5,("samr_reply_close_hnd: %d\n", __LINE__));

	/* store the response in the SMB stream */
	samr_io_r_close_hnd("", &r_u, rdata, 0);

	DEBUG(5,("samr_reply_close_hnd: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_close_hnd
 ********************************************************************/
static BOOL api_samr_close_hnd(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_CLOSE_HND q_u;

	/* grab the samr unknown 1 */
	samr_io_q_close_hnd("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_close_hnd(&q_u, rdata);

	return True;
}


/*******************************************************************
 samr_reply_open_domain
 ********************************************************************/
static void samr_reply_open_domain(SAMR_Q_OPEN_DOMAIN *q_u,
				prs_struct *rdata)
{
	SAMR_R_OPEN_DOMAIN r_u;
	BOOL pol_open = False;

	r_u.status = 0x0;

	/* find the connection policy handle. */
	if (r_u.status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->connect_pol)) == -1))
	{
		r_u.status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	/* get a (unique) handle.  open a policy on it. */
	if (r_u.status == 0x0 && !(pol_open = open_lsa_policy_hnd(&(r_u.domain_pol))))
	{
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	/* associate the domain SID with the (unique) handle. */
	if (r_u.status == 0x0 && !set_lsa_policy_samr_sid(&(r_u.domain_pol), &(q_u->dom_sid.sid)))
	{
		/* oh, whoops.  don't know what error message to return, here */
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	if (r_u.status != 0 && pol_open)
	{
		close_lsa_policy_hnd(&(r_u.domain_pol));
	}

	DEBUG(5,("samr_open_domain: %d\n", __LINE__));

	/* store the response in the SMB stream */
	samr_io_r_open_domain("", &r_u, rdata, 0);

	DEBUG(5,("samr_open_domain: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_open_domain
 ********************************************************************/
static BOOL api_samr_open_domain(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_OPEN_DOMAIN q_u;

	/* grab the samr open */
	samr_io_q_open_domain("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_open_domain(&q_u, rdata);

	return True;
}


/*******************************************************************
 samr_reply_unknown_2c
 ********************************************************************/
static void samr_reply_unknown_2c(SAMR_Q_UNKNOWN_2C *q_u,
				prs_struct *rdata)
{
	SAMR_R_UNKNOWN_2C r_u;
	uint32 status = 0x0;

	/* find the policy handle.  open a policy on it. */
	if (status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->user_pol)) == -1))
	{
		status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	/* find the user's rid */
	if ((status == 0x0) && (get_lsa_policy_samr_rid(&(q_u->user_pol)) == 0xffffffff))
	{
		status = NT_STATUS_OBJECT_TYPE_MISMATCH;
	}

	init_samr_r_unknown_2c(&r_u, status);

	DEBUG(5,("samr_unknown_2c: %d\n", __LINE__));

	/* store the response in the SMB stream */
	samr_io_r_unknown_2c("", &r_u, rdata, 0);

	DEBUG(5,("samr_unknown_2c: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_unknown_2c
 ********************************************************************/
static BOOL api_samr_unknown_2c(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_UNKNOWN_2C q_u;

	/* grab the samr open */
	samr_io_q_unknown_2c("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_unknown_2c(&q_u, rdata);

	return True;
}


/*******************************************************************
 samr_reply_unknown_3
 ********************************************************************/
static void samr_reply_unknown_3(SAMR_Q_UNKNOWN_3 *q_u,
				prs_struct *rdata)
{
	SAMR_R_UNKNOWN_3 r_u;
	DOM_SID3 sid[MAX_SAM_SIDS];
	uint32 rid;
	uint32 status;

	status = 0x0;

	/* find the policy handle.  open a policy on it. */
	if (status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->user_pol)) == -1))
	{
		status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	/* find the user's rid */
	if (status == 0x0 && (rid = get_lsa_policy_samr_rid(&(q_u->user_pol))) == 0xffffffff)
	{
		status = NT_STATUS_OBJECT_TYPE_MISMATCH;
	}

	if (status == 0x0)
	{
		DOM_SID user_sid;
		DOM_SID everyone_sid;

		user_sid = global_sam_sid;

		SMB_ASSERT_ARRAY(user_sid.sub_auths, user_sid.num_auths+1);

		/*
		 * Add the user RID.
		 */
		user_sid.sub_auths[user_sid.num_auths++] = rid;
		
			string_to_sid(&everyone_sid, "S-1-1");

			/* maybe need another 1 or 2 (S-1-5-0x20-0x220 and S-1-5-20-0x224) */
			/* these two are DOMAIN_ADMIN and DOMAIN_ACCT_OP group RIDs */
			init_dom_sid3(&(sid[0]), 0x035b, 0x0002, &everyone_sid);
			init_dom_sid3(&(sid[1]), 0x0044, 0x0002, &user_sid);
	}

	init_samr_r_unknown_3(&r_u,
				0x0001, 0x8004,
				0x00000014, 0x0002, 0x0070,
				2, sid, status);

	DEBUG(5,("samr_unknown_3: %d\n", __LINE__));

	/* store the response in the SMB stream */
	samr_io_r_unknown_3("", &r_u, rdata, 0);

	DEBUG(5,("samr_unknown_3: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_unknown_3
 ********************************************************************/
static BOOL api_samr_unknown_3(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_UNKNOWN_3 q_u;

	/* grab the samr open */
	samr_io_q_unknown_3("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_unknown_3(&q_u, rdata);

	return True;
}


/*******************************************************************
 samr_reply_enum_dom_users
 ********************************************************************/
static void samr_reply_enum_dom_users(SAMR_Q_ENUM_DOM_USERS *q_u,
				prs_struct *rdata)
{
	SAMR_R_ENUM_DOM_USERS r_e;
	SAM_USER_INFO_21 pass[MAX_SAM_ENTRIES];
	int num_entries;
	int total_entries;

	r_e.status = 0x0;
	r_e.total_num_entries = 0;

	/* find the policy handle.  open a policy on it. */
	if (r_e.status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->pol)) == -1))
	{
		r_e.status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	DEBUG(5,("samr_reply_enum_dom_users: %d\n", __LINE__));

	become_root(True);
	get_sampwd_entries(pass, 0, &total_entries, &num_entries, MAX_SAM_ENTRIES, q_u->acb_mask);
	unbecome_root(True);

	init_samr_r_enum_dom_users(&r_e, total_entries,
	                           q_u->unknown_0, num_entries,
	                           pass, r_e.status);

	/* store the response in the SMB stream */
	samr_io_r_enum_dom_users("", &r_e, rdata, 0);

	DEBUG(5,("samr_enum_dom_users: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_enum_dom_users
 ********************************************************************/
static BOOL api_samr_enum_dom_users(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_ENUM_DOM_USERS q_e;

	/* grab the samr open */
	samr_io_q_enum_dom_users("", &q_e, data, 0);

	/* construct reply. */
	samr_reply_enum_dom_users(&q_e, rdata);

	return True;
}


/*******************************************************************
 samr_reply_enum_dom_groups
 ********************************************************************/
static void samr_reply_enum_dom_groups(SAMR_Q_ENUM_DOM_GROUPS *q_u,
				prs_struct *rdata)
{
	SAMR_R_ENUM_DOM_GROUPS r_e;
	SAM_USER_INFO_21 pass[MAX_SAM_ENTRIES];
	int num_entries;
	BOOL got_grps;
	char *dummy_group = "Domain Admins";

	r_e.status = 0x0;
	r_e.num_entries = 0;

	/* find the policy handle.  open a policy on it. */
	if (r_e.status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->pol)) == -1))
	{
		r_e.status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	DEBUG(5,("samr_reply_enum_dom_groups: %d\n", __LINE__));

	got_grps = True;
	num_entries = 1;
	init_unistr2(&(pass[0].uni_user_name), dummy_group, strlen(dummy_group));
	pass[0].user_rid = DOMAIN_GROUP_RID_ADMINS;

	if (r_e.status == 0 && got_grps)
	{
		init_samr_r_enum_dom_groups(&r_e, q_u->start_idx, num_entries, pass, r_e.status);
	}

	/* store the response in the SMB stream */
	samr_io_r_enum_dom_groups("", &r_e, rdata, 0);

	DEBUG(5,("samr_enum_dom_groups: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_enum_dom_groups
 ********************************************************************/
static BOOL api_samr_enum_dom_groups(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_ENUM_DOM_GROUPS q_e;

	/* grab the samr open */
	samr_io_q_enum_dom_groups("", &q_e, data, 0);

	/* construct reply. */
	samr_reply_enum_dom_groups(&q_e, rdata);

	return True;
}

/*******************************************************************
 samr_reply_enum_dom_aliases
 ********************************************************************/
static void samr_reply_enum_dom_aliases(SAMR_Q_ENUM_DOM_ALIASES *q_u,
				prs_struct *rdata)
{
	SAMR_R_ENUM_DOM_ALIASES r_e;
	SAM_USER_INFO_21 pass[MAX_SAM_ENTRIES];
	int num_entries = 0;
	DOM_SID sid;
	fstring sid_str;
	fstring sam_sid_str;
	struct group *grp;

	r_e.status = 0x0;
	r_e.num_entries = 0;

	/* find the policy handle.  open a policy on it. */
	if (r_e.status == 0x0 && !get_lsa_policy_samr_sid(&q_u->pol, &sid))
	{
		r_e.status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	sid_to_string(sid_str, &sid);
	sid_to_string(sam_sid_str, &global_sam_sid);

	DEBUG(5,("samr_reply_enum_dom_aliases: sid %s\n", sid_str));

	/* well-known aliases */
	if (strequal(sid_str, "S-1-5-32"))
	{
		char *name;
		while (num_entries < MAX_SAM_ENTRIES && ((name = builtin_alias_rids[num_entries].name) != NULL))
		{
			init_unistr2(&(pass[num_entries].uni_user_name), name, strlen(name));
			pass[num_entries].user_rid = builtin_alias_rids[num_entries].rid;
			num_entries++;
		}
	}
	else if (strequal(sid_str, sam_sid_str))
	{
		char *name;
		/* local aliases */
		/* we return the UNIX groups here.  This seems to be the right */
		/* thing to do, since NT member servers return their local     */
                /* groups in the same situation.                               */
		setgrent();

		while (num_entries < MAX_SAM_ENTRIES && ((grp = getgrent()) != NULL))
		{
			name = grp->gr_name;
			init_unistr2(&(pass[num_entries].uni_user_name), name, strlen(name));
			pass[num_entries].user_rid = pdb_gid_to_group_rid(grp->gr_gid);
			num_entries++;
		}

		endgrent();
	}
		
	init_samr_r_enum_dom_aliases(&r_e, num_entries, pass, r_e.status);

	/* store the response in the SMB stream */
	samr_io_r_enum_dom_aliases("", &r_e, rdata, 0);

	DEBUG(5,("samr_enum_dom_aliases: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_enum_dom_aliases
 ********************************************************************/
static BOOL api_samr_enum_dom_aliases(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_ENUM_DOM_ALIASES q_e;

	/* grab the samr open */
	samr_io_q_enum_dom_aliases("", &q_e, data, 0);

	/* construct reply. */
	samr_reply_enum_dom_aliases(&q_e, rdata);

	return True;
}


/*******************************************************************
 samr_reply_query_dispinfo
 ********************************************************************/
static void samr_reply_query_dispinfo(SAMR_Q_QUERY_DISPINFO *q_u,
				prs_struct *rdata)
{
	SAMR_R_QUERY_DISPINFO r_e;
	SAM_INFO_CTR ctr;
	SAM_INFO_1 info1;
	SAM_INFO_2 info2;
	SAM_USER_INFO_21 pass[MAX_SAM_ENTRIES];
	int num_entries = 0;
	int total_entries = 0;
	BOOL got_pwds;
	uint16 switch_level = 0x0;

	ZERO_STRUCT(r_e);

	r_e.status = 0x0;

	DEBUG(5,("samr_reply_query_dispinfo: %d\n", __LINE__));

	/* find the policy handle.  open a policy on it. */
	if (r_e.status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->pol)) == -1))
	{
		r_e.status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
		DEBUG(5,("samr_reply_query_dispinfo: invalid handle\n"));
	}

	if (r_e.status == 0x0)
	{
		become_root(True);
		got_pwds = get_passwd_entries(pass, q_u->start_idx, &total_entries, &num_entries, MAX_SAM_ENTRIES, 0);
		unbecome_root(True);

		switch (q_u->switch_level)
		{
			case 0x1:
			{
			
				/* query disp info is for users */
				switch_level = 0x1;
				init_sam_info_1(&info1, ACB_NORMAL,
					q_u->start_idx, num_entries, pass);

				ctr.sam.info1 = &info1;

				break;
			}
			case 0x2:
			{
				/* query disp info is for servers */
				switch_level = 0x2;
				init_sam_info_2(&info2, ACB_WSTRUST,
					q_u->start_idx, num_entries, pass);

				ctr.sam.info2 = &info2;

				break;
			}
		}
	}

	if (r_e.status == 0)
	{
		init_samr_r_query_dispinfo(&r_e, switch_level, &ctr, r_e.status);
	}

	/* store the response in the SMB stream */
	samr_io_r_query_dispinfo("", &r_e, rdata, 0);

	DEBUG(5,("samr_query_dispinfo: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_query_dispinfo
 ********************************************************************/
static BOOL api_samr_query_dispinfo(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_QUERY_DISPINFO q_e;

	/* grab the samr open */
	samr_io_q_query_dispinfo("", &q_e, data, 0);

	/* construct reply. */
	samr_reply_query_dispinfo(&q_e, rdata);

	return True;
}


/*******************************************************************
 samr_reply_query_aliasinfo
 ********************************************************************/
static void samr_reply_query_aliasinfo(SAMR_Q_QUERY_ALIASINFO *q_u,
				prs_struct *rdata)
{
	SAMR_R_QUERY_ALIASINFO r_e;

	r_e.status = 0x0;
	r_e.ptr = 0;

	/* find the policy handle.  open a policy on it. */
	if (r_e.status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->pol)) == -1))
	{
		r_e.status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	DEBUG(5,("samr_reply_query_aliasinfo: %d\n", __LINE__));

	if (r_e.status == 0x0)
	{
		if (q_u->switch_level != 3)
		{
			r_e.status = NT_STATUS_INVALID_INFO_CLASS;
		}
	}

	init_samr_r_query_aliasinfo(&r_e, q_u->switch_level,
	                    "local UNIX group",
		                r_e.status);

	/* store the response in the SMB stream */
	samr_io_r_query_aliasinfo("", &r_e, rdata, 0);

	DEBUG(5,("samr_query_aliasinfo: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_query_aliasinfo
 ********************************************************************/
static BOOL api_samr_query_aliasinfo(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_QUERY_ALIASINFO q_e;

	/* grab the samr open */
	samr_io_q_query_aliasinfo("", &q_e, data, 0);

	/* construct reply. */
	samr_reply_query_aliasinfo(&q_e, rdata);

	return True;
}


/*******************************************************************
 samr_reply_lookup_ids
 ********************************************************************/
static void samr_reply_lookup_ids(SAMR_Q_LOOKUP_IDS *q_u,
				prs_struct *rdata)
{
	uint32 rid[MAX_SAM_ENTRIES];
	uint32 status     = 0;
	int num_rids = q_u->num_sids1;

	SAMR_R_LOOKUP_IDS r_u;

	DEBUG(5,("samr_lookup_ids: %d\n", __LINE__));

	if (num_rids > MAX_SAM_ENTRIES)
	{
		num_rids = MAX_SAM_ENTRIES;
		DEBUG(5,("samr_lookup_ids: truncating entries to %d\n", num_rids));
	}

#if 0
	int i;
	SMB_ASSERT_ARRAY(q_u->uni_user_name, num_rids);

	for (i = 0; i < num_rids && status == 0; i++)
	{
		struct sam_passwd *sam_pass;
		fstring user_name;


		fstrcpy(user_name, unistrn2(q_u->uni_user_name[i].buffer,
		                            q_u->uni_user_name[i].uni_str_len));

		/* find the user account */
		become_root(True);
		sam_pass = get_smb21pwd_entry(user_name, 0);
		unbecome_root(True);

		if (sam_pass == NULL)
		{
			status = 0xC0000000 | NT_STATUS_NO_SUCH_USER;
			rid[i] = 0;
		}
		else
		{
			rid[i] = sam_pass->user_rid;
		}
	}
#endif

	num_rids = 1;
	rid[0] = BUILTIN_ALIAS_RID_USERS;

	init_samr_r_lookup_ids(&r_u, num_rids, rid, status);

	/* store the response in the SMB stream */
	samr_io_r_lookup_ids("", &r_u, rdata, 0);

	DEBUG(5,("samr_lookup_ids: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_lookup_ids
 ********************************************************************/
static BOOL api_samr_lookup_ids(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_LOOKUP_IDS q_u;

	/* grab the samr 0x10 */
	samr_io_q_lookup_ids("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_lookup_ids(&q_u, rdata);

	return True;
}

/*******************************************************************
 samr_reply_lookup_names
 ********************************************************************/

static BOOL samr_reply_lookup_names(SAMR_Q_LOOKUP_NAMES *q_u,
				prs_struct *rdata)
{
	uint32 rid[MAX_SAM_ENTRIES];
	uint8  type[MAX_SAM_ENTRIES];
	uint32 status = 0;
	int i;
	int num_rids = q_u->num_names1;
	DOM_SID pol_sid;

	SAMR_R_LOOKUP_NAMES r_u;

	DEBUG(5,("samr_lookup_names: %d\n", __LINE__));

	ZERO_ARRAY(rid);
	ZERO_ARRAY(type);

	if (!get_lsa_policy_samr_sid(&q_u->pol, &pol_sid)) {
        status = 0xC0000000 | NT_STATUS_OBJECT_TYPE_MISMATCH;
		init_samr_r_lookup_names(&r_u, 0, rid, type, status);
		if(!samr_io_r_lookup_names("", &r_u, rdata, 0)) {
			DEBUG(0,("samr_reply_lookup_names: failed to marshall SAMR_R_LOOKUP_NAMES.\n"));
        	return False;
		}
		return True;
    }

	if (num_rids > MAX_SAM_ENTRIES) {
		num_rids = MAX_SAM_ENTRIES;
		DEBUG(5,("samr_lookup_names: truncating entries to %d\n", num_rids));
	}

	SMB_ASSERT_ARRAY(q_u->uni_name, num_rids);

	for (i = 0; i < num_rids; i++) {
		fstring name;

		status = 0xC0000000 | NT_STATUS_NONE_MAPPED;

		rid [i] = 0xffffffff;
		type[i] = SID_NAME_UNKNOWN;

		fstrcpy(name, dos_unistrn2(q_u->uni_name[i].buffer,
				q_u->uni_name[i].uni_str_len));

		if(sid_equal(&pol_sid, &global_sam_sid)) {
			DOM_SID sid;

			if(lookup_local_name(global_myname, name, &sid, &type[i])) {
				sid_split_rid( &sid, &rid[i]);
				status = 0;
			}
		}
	}

	init_samr_r_lookup_names(&r_u, num_rids, rid, type, status);

	/* store the response in the SMB stream */
	if(!samr_io_r_lookup_names("", &r_u, rdata, 0)) {
		DEBUG(0,("samr_reply_lookup_names: failed to marshall SAMR_R_LOOKUP_NAMES.\n"));
		return False;
	}

	DEBUG(5,("samr_lookup_names: %d\n", __LINE__));

	return True;
}

/*******************************************************************
 api_samr_lookup_names
 ********************************************************************/

static BOOL api_samr_lookup_names(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_LOOKUP_NAMES q_u;

	memset(&q_u, '\0', sizeof(q_u));

	/* grab the samr lookup names */
	if(!samr_io_q_lookup_names("", &q_u, data, 0)) {
		DEBUG(0,("api_samr_lookup_names: failed to unmarshall SAMR_Q_LOOKUP_NAMES.\n"));
		return False;
	}

	/* construct reply.  always indicate success */
	if(!samr_reply_lookup_names(&q_u, rdata))
		return False;

	return True;
}

/*******************************************************************
 samr_reply_chgpasswd_user
 ********************************************************************/

static BOOL samr_reply_chgpasswd_user(SAMR_Q_CHGPASSWD_USER *q_u,
				prs_struct *rdata)
{
	SAMR_R_CHGPASSWD_USER r_u;
	uint32 status = 0x0;
	fstring user_name;
	fstring wks;

	fstrcpy(user_name, dos_unistrn2(q_u->uni_user_name.buffer, q_u->uni_user_name.uni_str_len));
	fstrcpy(wks      , dos_unistrn2(q_u->uni_dest_host.buffer, q_u->uni_dest_host.uni_str_len));

	DEBUG(5,("samr_chgpasswd_user: user: %s wks: %s\n", user_name, wks));

	if (!pass_oem_change(user_name,
	                     q_u->lm_newpass.pass, q_u->lm_oldhash.hash,
	                     q_u->nt_newpass.pass, q_u->nt_oldhash.hash))
	{
		status = 0xC0000000 | NT_STATUS_WRONG_PASSWORD;
	}

	init_samr_r_chgpasswd_user(&r_u, status);

	/* store the response in the SMB stream */
	if(!samr_io_r_chgpasswd_user("", &r_u, rdata, 0)) {
		DEBUG(0,("samr_reply_chgpasswd_user: Failed to marshall SAMR_R_CHGPASSWD_USER struct.\n" ));
		return False;
	}

	DEBUG(5,("samr_chgpasswd_user: %d\n", __LINE__));
	return True;
}

/*******************************************************************
 api_samr_chgpasswd_user
 ********************************************************************/

static BOOL api_samr_chgpasswd_user(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_CHGPASSWD_USER q_u;

	/* unknown 38 command */
	if (!samr_io_q_chgpasswd_user("", &q_u, data, 0)) {
		DEBUG(0,("api_samr_chgpasswd_user: samr_io_q_chgpasswd_user failed to parse RPC packet.\n"));
		return False;
	}

	/* construct reply. */
	if(!samr_reply_chgpasswd_user(&q_u, rdata)) {
		DEBUG(0,("api_samr_chgpasswd_user: samr_reply_chgpasswd_user failed to create reply packet.\n"));
		return False;
	}

	return True;
}


/*******************************************************************
 samr_reply_unknown_38
 ********************************************************************/
static void samr_reply_unknown_38(SAMR_Q_UNKNOWN_38 *q_u,
				prs_struct *rdata)
{
	SAMR_R_UNKNOWN_38 r_u;

	DEBUG(5,("samr_unknown_38: %d\n", __LINE__));

	init_samr_r_unknown_38(&r_u);

	/* store the response in the SMB stream */
	samr_io_r_unknown_38("", &r_u, rdata, 0);

	DEBUG(5,("samr_unknown_38: %d\n", __LINE__));
}

/*******************************************************************
 api_samr_unknown_38
 ********************************************************************/
static BOOL api_samr_unknown_38(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_UNKNOWN_38 q_u;

	/* unknown 38 command */
	samr_io_q_unknown_38("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_unknown_38(&q_u, rdata);

	return True;
}


/*******************************************************************
 samr_reply_unknown_12
 ********************************************************************/
static void samr_reply_unknown_12(SAMR_Q_UNKNOWN_12 *q_u,
				prs_struct *rdata)
{
	fstring group_names[MAX_SAM_ENTRIES];
	uint32  group_attrs[MAX_SAM_ENTRIES];
	uint32 status     = 0;
	int num_gids = q_u->num_gids1;

	SAMR_R_UNKNOWN_12 r_u;

	DEBUG(5,("samr_unknown_12: %d\n", __LINE__));

	/* find the policy handle.  open a policy on it. */
	if (status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->pol)) == -1))
	{
		status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	if (status == 0x0)
	{
		int i;
		if (num_gids > MAX_SAM_ENTRIES)
		{
			num_gids = MAX_SAM_ENTRIES;
			DEBUG(5,("samr_unknown_12: truncating entries to %d\n", num_gids));
		}

		for (i = 0; i < num_gids && status == 0; i++)
		{
			fstrcpy(group_names[i], "dummy group");
			group_attrs[i] = 0x2;
		}
	}

	init_samr_r_unknown_12(&r_u, num_gids, group_names, group_attrs, status);

	/* store the response in the SMB stream */
	samr_io_r_unknown_12("", &r_u, rdata, 0);

	DEBUG(5,("samr_unknown_12: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_unknown_12
 ********************************************************************/
static BOOL api_samr_unknown_12(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_UNKNOWN_12 q_u;

	/* grab the samr lookup names */
	samr_io_q_unknown_12("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_unknown_12(&q_u, rdata);

	return True;
}


/*******************************************************************
 samr_reply_open_user
 ********************************************************************/
static void samr_reply_open_user(SAMR_Q_OPEN_USER *q_u,
				prs_struct *rdata,
				int status)
{
	SAMR_R_OPEN_USER r_u;
	struct sam_passwd *sam_pass;
	BOOL pol_open = False;

	/* set up the SAMR open_user response */
	memset((char *)r_u.user_pol.data, '\0', POL_HND_SIZE);

	r_u.status = 0x0;

	/* find the policy handle.  open a policy on it. */
	if (r_u.status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->domain_pol)) == -1))
	{
		r_u.status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	/* get a (unique) handle.  open a policy on it. */
	if (r_u.status == 0x0 && !(pol_open = open_lsa_policy_hnd(&(r_u.user_pol))))
	{
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	become_root(True);
	sam_pass = getsam21pwrid(q_u->user_rid);
	unbecome_root(True);

	/* check that the RID exists in our domain. */
	if (r_u.status == 0x0 && sam_pass == NULL)
	{
		r_u.status = 0xC0000000 | NT_STATUS_NO_SUCH_USER;
	}

	/* associate the RID with the (unique) handle. */
	if (r_u.status == 0x0 && !set_lsa_policy_samr_rid(&(r_u.user_pol), q_u->user_rid))
	{
		/* oh, whoops.  don't know what error message to return, here */
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	if (r_u.status != 0 && pol_open)
	{
		close_lsa_policy_hnd(&(r_u.user_pol));
	}

	DEBUG(5,("samr_open_user: %d\n", __LINE__));

	/* store the response in the SMB stream */
	samr_io_r_open_user("", &r_u, rdata, 0);

	DEBUG(5,("samr_open_user: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_open_user
 ********************************************************************/
static BOOL api_samr_open_user(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_OPEN_USER q_u;

	/* grab the samr unknown 22 */
	samr_io_q_open_user("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_open_user(&q_u, rdata, 0x0);

	return True;
}


/*************************************************************************
 get_user_info_10
 *************************************************************************/
static BOOL get_user_info_10(SAM_USER_INFO_10 *id10, uint32 user_rid)
{
	struct smb_passwd *smb_pass;

	if (!pdb_rid_is_user(user_rid))
	{
		DEBUG(4,("RID 0x%x is not a user RID\n", user_rid));
		return False;
	}

	become_root(True);
	smb_pass = getsmbpwrid(user_rid);
	unbecome_root(True);

	if (smb_pass == NULL)
	{
		DEBUG(4,("User 0x%x not found\n", user_rid));
		return False;
	}

	DEBUG(3,("User:[%s]\n", smb_pass->smb_name));

	init_sam_user_info10(id10, smb_pass->acct_ctrl); 

	return True;
}

/*************************************************************************
 get_user_info_21
 *************************************************************************/
static BOOL get_user_info_21(SAM_USER_INFO_21 *id21, uint32 user_rid)
{
	NTTIME dummy_time;
	struct sam_passwd *sam_pass;
	LOGON_HRS hrs;
	int i;

	if (!pdb_rid_is_user(user_rid))
	{
		DEBUG(4,("RID 0x%x is not a user RID\n", user_rid));
		return False;
	}

	become_root(True);
	sam_pass = getsam21pwrid(user_rid);
	unbecome_root(True);

	if (sam_pass == NULL)
	{
		DEBUG(4,("User 0x%x not found\n", user_rid));
		return False;
	}

	DEBUG(3,("User:[%s]\n", sam_pass->smb_name));

	dummy_time.low  = 0xffffffff;
	dummy_time.high = 0x7fffffff;

	DEBUG(5,("get_user_info_21 - TODO: convert unix times to NTTIMEs\n"));

	/* create a LOGON_HRS structure */
	hrs.len = sam_pass->hours_len;
	SMB_ASSERT_ARRAY(hrs.hours, hrs.len);
	for (i = 0; i < hrs.len; i++)
	{
		hrs.hours[i] = sam_pass->hours[i];
	}

	init_sam_user_info21(id21,

			   &dummy_time, /* logon_time */
			   &dummy_time, /* logoff_time */
			   &dummy_time, /* kickoff_time */
			   &dummy_time, /* pass_last_set_time */
			   &dummy_time, /* pass_can_change_time */
			   &dummy_time, /* pass_must_change_time */

			   sam_pass->smb_name, /* user_name */
			   sam_pass->full_name, /* full_name */
			   sam_pass->home_dir, /* home_dir */
			   sam_pass->dir_drive, /* dir_drive */
			   sam_pass->logon_script, /* logon_script */
			   sam_pass->profile_path, /* profile_path */
			   sam_pass->acct_desc, /* description */
			   sam_pass->workstations, /* workstations user can log in from */
			   sam_pass->unknown_str, /* don't know, yet */
			   sam_pass->munged_dial, /* dialin info.  contains dialin path and tel no */

			   sam_pass->user_rid, /* RID user_id */
			   sam_pass->group_rid, /* RID group_id */
		       sam_pass->acct_ctrl,

	           sam_pass->unknown_3, /* unknown_3 */
		       sam_pass->logon_divs, /* divisions per week */
			   &hrs, /* logon hours */
		       sam_pass->unknown_5,
		       sam_pass->unknown_6);

	return True;
}

/*******************************************************************
 samr_reply_query_userinfo
 ********************************************************************/
static void samr_reply_query_userinfo(SAMR_Q_QUERY_USERINFO *q_u,
				prs_struct *rdata)
{
	SAMR_R_QUERY_USERINFO r_u;
#if 0
	SAM_USER_INFO_11 id11;
#endif
	SAM_USER_INFO_10 id10;
	SAM_USER_INFO_21 id21;
	void *info = NULL;

	uint32 status = 0x0;
	uint32 rid = 0x0;

	DEBUG(5,("samr_reply_query_userinfo: %d\n", __LINE__));

	/* search for the handle */
	if (status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->pol)) == -1))
	{
		status = NT_STATUS_INVALID_HANDLE;
	}

	/* find the user's rid */
	if (status == 0x0 && (rid = get_lsa_policy_samr_rid(&(q_u->pol))) == 0xffffffff)
	{
		status = NT_STATUS_OBJECT_TYPE_MISMATCH;
	}

	DEBUG(5,("samr_reply_query_userinfo: rid:0x%x\n", rid));

	/* ok!  user info levels (there are lots: see MSDEV help), off we go... */
	if (status == 0x0)
	{
		switch (q_u->switch_value)
		{
			case 0x10:
			{
				info = (void*)&id10;
				status = get_user_info_10(&id10, rid) ? 0 : NT_STATUS_NO_SUCH_USER;
				break;
			}
#if 0
/* whoops - got this wrong.  i think.  or don't understand what's happening. */
			case 0x11:
			{
				NTTIME expire;
				info = (void*)&id11;
				
				expire.low  = 0xffffffff;
				expire.high = 0x7fffffff;

				make_sam_user_info11(&id11, &expire, "BROOKFIELDS$", 0x03ef, 0x201, 0x0080);

				break;
			}
#endif
			case 21:
			{
				info = (void*)&id21;
				status = get_user_info_21(&id21, rid) ? 0 : NT_STATUS_NO_SUCH_USER;
				break;
			}

			default:
			{
				status = NT_STATUS_INVALID_INFO_CLASS;

				break;
			}
		}
	}

	init_samr_r_query_userinfo(&r_u, q_u->switch_value, info, status);

	/* store the response in the SMB stream */
	samr_io_r_query_userinfo("", &r_u, rdata, 0);

	DEBUG(5,("samr_reply_query_userinfo: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_query_userinfo
 ********************************************************************/
static BOOL api_samr_query_userinfo(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_QUERY_USERINFO q_u;

	/* grab the samr unknown 24 */
	samr_io_q_query_userinfo("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_query_userinfo(&q_u, rdata);

	return True;
}


/*******************************************************************
 samr_reply_query_usergroups
 ********************************************************************/
static void samr_reply_query_usergroups(SAMR_Q_QUERY_USERGROUPS *q_u,
				prs_struct *rdata)
{
	SAMR_R_QUERY_USERGROUPS r_u;
	uint32 status = 0x0;

	struct sam_passwd *sam_pass;
	DOM_GID *gids = NULL;
	int num_groups = 0;
	uint32 rid;

	DEBUG(5,("samr_query_usergroups: %d\n", __LINE__));

	/* find the policy handle.  open a policy on it. */
	if (status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->pol)) == -1))
	{
		status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
	}

	/* find the user's rid */
	if (status == 0x0 && (rid = get_lsa_policy_samr_rid(&(q_u->pol))) == 0xffffffff)
	{
		status = NT_STATUS_OBJECT_TYPE_MISMATCH;
	}

	if (status == 0x0)
	{
		become_root(True);
		sam_pass = getsam21pwrid(rid);
		unbecome_root(True);

		if (sam_pass == NULL)
		{
			status = 0xC0000000 | NT_STATUS_NO_SUCH_USER;
		}
	}

	if (status == 0x0)
	{
		pstring groups;
		get_domain_user_groups(groups, sam_pass->smb_name);
                gids = NULL;
		num_groups = make_dom_gids(groups, &gids);
	}

	/* construct the response.  lkclXXXX: gids are not copied! */
	init_samr_r_query_usergroups(&r_u, num_groups, gids, status);

	/* store the response in the SMB stream */
	samr_io_r_query_usergroups("", &r_u, rdata, 0);

	if (gids)
	{
		free((char *)gids);
	}

	DEBUG(5,("samr_query_usergroups: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_query_usergroups
 ********************************************************************/
static BOOL api_samr_query_usergroups(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_QUERY_USERGROUPS q_u;
	/* grab the samr unknown 32 */
	samr_io_q_query_usergroups("", &q_u, data, 0);

	/* construct reply. */
	samr_reply_query_usergroups(&q_u, rdata);

	return True;
}


/*******************************************************************
 samr_reply_query_dom_info
 ********************************************************************/
static void samr_reply_query_dom_info(SAMR_Q_QUERY_DOMAIN_INFO *q_u,
				prs_struct *rdata)
{
	SAMR_R_QUERY_DOMAIN_INFO r_u;
	SAM_UNK_CTR ctr;
	uint16 switch_value = 0x0;
	uint32 status = 0x0;

	ZERO_STRUCT(r_u);
	ZERO_STRUCT(ctr);

	r_u.ctr = &ctr;

	DEBUG(5,("samr_reply_query_dom_info: %d\n", __LINE__));

	/* find the policy handle.  open a policy on it. */
	if (r_u.status == 0x0 && (find_lsa_policy_by_hnd(&(q_u->domain_pol)) == -1))
	{
		r_u.status = 0xC0000000 | NT_STATUS_INVALID_HANDLE;
		DEBUG(5,("samr_reply_query_dom_info: invalid handle\n"));
	}

	if (status == 0x0)
	{
		switch (q_u->switch_value)
		{
			case 0x02:
			{
				switch_value = 0x2;
				init_unk_info2(&ctr.info.inf2, global_myworkgroup, global_myname);

				break;
			}
			default:
			{
				status = 0xC0000000 | NT_STATUS_INVALID_INFO_CLASS;
				break;
			}
		}
	}

	init_samr_r_query_dom_info(&r_u, switch_value, &ctr, status);

	/* store the response in the SMB stream */
	samr_io_r_query_dom_info("", &r_u, rdata, 0);

	DEBUG(5,("samr_query_dom_info: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_query_dom_info
 ********************************************************************/
static BOOL api_samr_query_dom_info(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_QUERY_DOMAIN_INFO q_e;

	/* grab the samr unknown 8 command */
	samr_io_q_query_dom_info("", &q_e, data, 0);

	/* construct reply. */
	samr_reply_query_dom_info(&q_e, rdata);

	return True;
}



/*******************************************************************
 samr_reply_unknown_32
 ********************************************************************/
static void samr_reply_unknown_32(SAMR_Q_UNKNOWN_32 *q_u,
				prs_struct *rdata,
				int status)
{
	int i;
	SAMR_R_UNKNOWN_32 r_u;

	/* set up the SAMR unknown_32 response */
	memset((char *)r_u.pol.data, '\0', POL_HND_SIZE);
	if (status == 0)
	{
		for (i = 4; i < POL_HND_SIZE; i++)
		{
			r_u.pol.data[i] = i+1;
		}
	}

	init_dom_rid4(&(r_u.rid4), 0x0030, 0, 0);
	r_u.status    = status;

	DEBUG(5,("samr_unknown_32: %d\n", __LINE__));

	/* store the response in the SMB stream */
	samr_io_r_unknown_32("", &r_u, rdata, 0);

	DEBUG(5,("samr_unknown_32: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_unknown_32
 ********************************************************************/
static BOOL api_samr_unknown_32(prs_struct *data, prs_struct *rdata)
{
	uint32 status = 0;
	struct sam_passwd *sam_pass;
	fstring mach_acct;

	SAMR_Q_UNKNOWN_32 q_u;

	/* grab the samr unknown 32 */
	samr_io_q_unknown_32("", &q_u, data, 0);

	/* find the machine account: tell the caller if it exists.
	   lkclXXXX i have *no* idea if this is a problem or not
	   or even if you are supposed to construct a different
	   reply if the account already exists...
	 */

	fstrcpy(mach_acct, dos_unistrn2(q_u.uni_mach_acct.buffer,
	                            q_u.uni_mach_acct.uni_str_len));

	become_root(True);
	sam_pass = getsam21pwnam(mach_acct);
	unbecome_root(True);

	if (sam_pass != NULL)
	{
		/* machine account exists: say so */
		status = 0xC0000000 | NT_STATUS_USER_EXISTS;
	}
	else
	{
		/* this could cause trouble... */
		DEBUG(0,("trouble!\n"));
		status = 0;
	}

	/* construct reply. */
	samr_reply_unknown_32(&q_u, rdata, status);

	return True;
}


/*******************************************************************
 samr_reply_connect_anon
 ********************************************************************/
static void samr_reply_connect_anon(SAMR_Q_CONNECT_ANON *q_u,
				prs_struct *rdata)
{
	SAMR_R_CONNECT_ANON r_u;
	BOOL pol_open = False;

	/* set up the SAMR connect_anon response */

	r_u.status = 0x0;
	/* get a (unique) handle.  open a policy on it. */
	if (r_u.status == 0x0 && !(pol_open = open_lsa_policy_hnd(&(r_u.connect_pol))))
	{
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	/* associate the domain SID with the (unique) handle. */
	if (r_u.status == 0x0 && !set_lsa_policy_samr_pol_status(&(r_u.connect_pol), q_u->unknown_0))
	{
		/* oh, whoops.  don't know what error message to return, here */
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	if (r_u.status != 0 && pol_open)
	{
		close_lsa_policy_hnd(&(r_u.connect_pol));
	}

	DEBUG(5,("samr_connect_anon: %d\n", __LINE__));

	/* store the response in the SMB stream */
	samr_io_r_connect_anon("", &r_u, rdata, 0);

	DEBUG(5,("samr_connect_anon: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_connect_anon
 ********************************************************************/
static BOOL api_samr_connect_anon(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_CONNECT_ANON q_u;

	/* grab the samr open policy */
	samr_io_q_connect_anon("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_connect_anon(&q_u, rdata);

	return True;
}

/*******************************************************************
 samr_reply_connect
 ********************************************************************/
static void samr_reply_connect(SAMR_Q_CONNECT *q_u,
				prs_struct *rdata)
{
	SAMR_R_CONNECT r_u;
	BOOL pol_open = False;

	/* set up the SAMR connect response */

	r_u.status = 0x0;
	/* get a (unique) handle.  open a policy on it. */
	if (r_u.status == 0x0 && !(pol_open = open_lsa_policy_hnd(&(r_u.connect_pol))))
	{
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	/* associate the domain SID with the (unique) handle. */
	if (r_u.status == 0x0 && !set_lsa_policy_samr_pol_status(&(r_u.connect_pol), q_u->unknown_0))
	{
		/* oh, whoops.  don't know what error message to return, here */
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	if (r_u.status != 0 && pol_open)
	{
		close_lsa_policy_hnd(&(r_u.connect_pol));
	}

	DEBUG(5,("samr_connect: %d\n", __LINE__));

	/* store the response in the SMB stream */
	samr_io_r_connect("", &r_u, rdata, 0);

	DEBUG(5,("samr_connect: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_connect
 ********************************************************************/
static BOOL api_samr_connect(prs_struct *data, prs_struct *rdata)
{
	SAMR_Q_CONNECT q_u;

	/* grab the samr open policy */
	samr_io_q_connect("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_connect(&q_u, rdata);

	return True;
}

/*******************************************************************
 samr_reply_open_alias
 ********************************************************************/
static void samr_reply_open_alias(SAMR_Q_OPEN_ALIAS *q_u,
				prs_struct *rdata)
{
	SAMR_R_OPEN_ALIAS r_u;
	BOOL pol_open = False;

	/* set up the SAMR open_alias response */

	r_u.status = 0x0;
	/* get a (unique) handle.  open a policy on it. */
	if (r_u.status == 0x0 && !(pol_open = open_lsa_policy_hnd(&(r_u.pol))))
	{
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	/* associate a RID with the (unique) handle. */
	if (r_u.status == 0x0 && !set_lsa_policy_samr_rid(&(r_u.pol), q_u->rid_alias))
	{
		/* oh, whoops.  don't know what error message to return, here */
		r_u.status = 0xC0000000 | NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	if (r_u.status != 0 && pol_open)
	{
		close_lsa_policy_hnd(&(r_u.pol));
	}

	DEBUG(5,("samr_open_alias: %d\n", __LINE__));

	/* store the response in the SMB stream */
	samr_io_r_open_alias("", &r_u, rdata, 0);

	DEBUG(5,("samr_open_alias: %d\n", __LINE__));

}

/*******************************************************************
 api_samr_open_alias
 ********************************************************************/
static BOOL api_samr_open_alias(prs_struct *data, prs_struct *rdata)
                                
{
	SAMR_Q_OPEN_ALIAS q_u;

	/* grab the samr open policy */
	samr_io_q_open_alias("", &q_u, data, 0);

	/* construct reply.  always indicate success */
	samr_reply_open_alias(&q_u, rdata);

	return True;
}

/*******************************************************************
 array of \PIPE\samr operations
 ********************************************************************/
static struct api_struct api_samr_cmds [] =
{
	{ "SAMR_CLOSE_HND"        , SAMR_CLOSE_HND        , api_samr_close_hnd        },
	{ "SAMR_CONNECT"          , SAMR_CONNECT          , api_samr_connect          },
	{ "SAMR_CONNECT_ANON"     , SAMR_CONNECT_ANON     , api_samr_connect_anon     },
	{ "SAMR_ENUM_DOM_USERS"   , SAMR_ENUM_DOM_USERS   , api_samr_enum_dom_users   },
	{ "SAMR_ENUM_DOM_GROUPS"  , SAMR_ENUM_DOM_GROUPS  , api_samr_enum_dom_groups  },
	{ "SAMR_ENUM_DOM_ALIASES" , SAMR_ENUM_DOM_ALIASES , api_samr_enum_dom_aliases },
	{ "SAMR_LOOKUP_IDS"       , SAMR_LOOKUP_IDS       , api_samr_lookup_ids       },
	{ "SAMR_LOOKUP_NAMES"     , SAMR_LOOKUP_NAMES     , api_samr_lookup_names     },
	{ "SAMR_OPEN_USER"        , SAMR_OPEN_USER        , api_samr_open_user        },
	{ "SAMR_QUERY_USERINFO"   , SAMR_QUERY_USERINFO   , api_samr_query_userinfo   },
	{ "SAMR_QUERY_DOMAIN_INFO", SAMR_QUERY_DOMAIN_INFO, api_samr_query_dom_info        },
	{ "SAMR_QUERY_USERGROUPS" , SAMR_QUERY_USERGROUPS , api_samr_query_usergroups },
	{ "SAMR_QUERY_DISPINFO"   , SAMR_QUERY_DISPINFO   , api_samr_query_dispinfo   },
	{ "SAMR_QUERY_ALIASINFO"  , SAMR_QUERY_ALIASINFO  , api_samr_query_aliasinfo  },
	{ "SAMR_0x32"             , 0x32                  , api_samr_unknown_32       },
	{ "SAMR_UNKNOWN_12"       , SAMR_UNKNOWN_12       , api_samr_unknown_12       },
	{ "SAMR_UNKNOWN_38"       , SAMR_UNKNOWN_38       , api_samr_unknown_38       },
	{ "SAMR_CHGPASSWD_USER"   , SAMR_CHGPASSWD_USER   , api_samr_chgpasswd_user   },
	{ "SAMR_OPEN_ALIAS"       , SAMR_OPEN_ALIAS       , api_samr_open_alias       },
	{ "SAMR_OPEN_DOMAIN"      , SAMR_OPEN_DOMAIN      , api_samr_open_domain      },
	{ "SAMR_UNKNOWN_3"        , SAMR_UNKNOWN_3        , api_samr_unknown_3        },
	{ "SAMR_UNKNOWN_2C"       , SAMR_UNKNOWN_2C       , api_samr_unknown_2c       },
	{ NULL                    , 0                     , NULL                      }
};

/*******************************************************************
 receives a samr pipe and responds.
 ********************************************************************/
BOOL api_samr_rpc(pipes_struct *p, prs_struct *data)
{
    return api_rpcTNP(p, "api_samr_rpc", api_samr_cmds, data);
}

#undef OLD_NTDOMAIN