/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
* Copyright 2015 Nexenta Systems, Inc. All rights reserved.
*/
/*
* This is the named pipe service for smbd.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <synch.h>
#include <unistd.h>
#include <fcntl.h>
#include <door.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <smbsrv/libsmb.h>
#include <smbsrv/libmlsvc.h>
#include <smbsrv/smb_xdr.h>
#include "smbd.h"
struct pipe_listener {
const char *name;
int max_allowed;
int max_seen;
int current;
pthread_t tid;
};
static void *pipesvc_listener(void *);
static void *pipesvc_worker(void *);
static int pipe_send(ndr_pipe_t *, void *, size_t);
static int pipe_recv(ndr_pipe_t *, void *, size_t);
mutex_t pipesvc_mutex = DEFAULTMUTEX;
int pipesvc_workers_max = 500;
int pipesvc_workers_cur = 0;
uint16_t pipe_max_msgsize = SMB_PIPE_MAX_MSGSIZE;
/*
* Allow more opens on SRVSVC because that's used by many clients
* to get the share list, etc.
*/
#define SRVSVC_MAX_OPENS 200
#define DEF_MAX_OPENS 50
#define NLISTENERS 11
static struct pipe_listener
pipe_listeners[NLISTENERS] = {
{ "eventlog", DEF_MAX_OPENS, 0, 0 },
{ "lsarpc", DEF_MAX_OPENS, 0, 0 },
{ "lsass", DEF_MAX_OPENS, 0, 0 },
{ "netdfs", DEF_MAX_OPENS, 0, 0 },
{ "netlogon", DEF_MAX_OPENS, 0, 0 },
{ "samr", DEF_MAX_OPENS, 0, 0 },
{ "spoolss", DEF_MAX_OPENS, 0, 0 },
{ "srvsvc", SRVSVC_MAX_OPENS, 0, 0 },
{ "svcctl", DEF_MAX_OPENS, 0, 0 },
{ "winreg", DEF_MAX_OPENS, 0, 0 },
{ "wkssvc", DEF_MAX_OPENS, 0, 0 },
};
static ndr_pipe_t *
np_new(struct pipe_listener *pl, int fid)
{
ndr_pipe_t *np;
size_t len;
/*
* Allocating ndr_pipe_t + smb_netuserinfo_t as one.
* We could just make that part of ndr_pipe_t, but
* that struct is opaque to libmlrpc.
*/
len = sizeof (*np) + sizeof (smb_netuserinfo_t);
np = malloc(len);
if (np == NULL)
return (NULL);
bzero(np, len);
np->np_listener = pl;
np->np_endpoint = pl->name;
np->np_user = (void*)(np + 1);
np->np_send = pipe_send;
np->np_recv = pipe_recv;
np->np_fid = fid;
np->np_max_xmit_frag = pipe_max_msgsize;
np->np_max_recv_frag = pipe_max_msgsize;
return (np);
}
static void
np_free(ndr_pipe_t *np)
{
(void) close(np->np_fid);
free(np);
}
/*
* Create the smbd opipe door service.
* Returns the door descriptor on success. Otherwise returns -1.
*/
int
smbd_pipesvc_start(void)
{
pthread_t tid;
pthread_attr_t tattr;
struct pipe_listener *pl;
int i, rc;
if (mlsvc_init() != 0) {
smbd_report("msrpc initialization failed");
return (-1);
}
(void) pthread_attr_init(&tattr);
(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
for (i = 0; i < NLISTENERS; i++) {
pl = &pipe_listeners[i];
pl->max_seen = 0;
if (strcasecmp(pl->name, "spoolss") == 0 &&
smb_config_getbool(SMB_CI_PRINT_ENABLE) == B_FALSE)
continue;
rc = pthread_create(&tid, &tattr, pipesvc_listener, pl);
if (rc != 0)
break;
pipe_listeners[i].tid = tid;
}
if (rc != 0) {
smbd_report("pipesvc pthread_create, %d", rc);
}
(void) pthread_attr_destroy(&tattr);
return (rc);
}
void
smbd_pipesvc_stop(void)
{
int i;
(void) mutex_lock(&pipesvc_mutex);
for (i = 0; i < NLISTENERS; i++) {
if (pipe_listeners[i].tid == 0)
continue;
(void) pthread_kill(pipe_listeners[i].tid, SIGTERM);
pipe_listeners[i].tid = 0;
}
(void) mutex_unlock(&pipesvc_mutex);
}
static void *
pipesvc_listener(void *varg)
{
struct sockaddr_un sa;
int err, listen_fd, newfd, snlen;
struct pipe_listener *pl = varg;
ndr_pipe_t *np;
pthread_t tid;
int rc;
listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (listen_fd < 0) {
smbd_report("pipesvc_listener, so_create: %d", errno);
return (NULL);
}
bzero(&sa, sizeof (sa));
sa.sun_family = AF_UNIX;
(void) snprintf(sa.sun_path, sizeof (sa.sun_path),
"%s/%s", SMB_PIPE_DIR, pl->name);
/* Bind it to a listening name. */
(void) unlink(sa.sun_path);
if (bind(listen_fd, (struct sockaddr *)&sa, sizeof (sa)) < 0) {
smbd_report("pipesvc_listener, so_bind: %d", errno);
(void) close(listen_fd);
return (NULL);
}
if (listen(listen_fd, SOMAXCONN) < 0) {
smbd_report("pipesvc_listener, listen: %d", errno);
(void) close(listen_fd);
return (NULL);
}
for (;;) {
snlen = sizeof (sa);
newfd = accept(listen_fd, (struct sockaddr *)&sa, &snlen);
if (newfd < 0) {
err = errno;
switch (err) {
case ECONNABORTED:
continue;
case EINTR:
/* normal termination */
goto out;
default:
smbd_report("pipesvc_listener, "
"accept failed: %d", errno);
}
smbd_report("pipesvc_listener, accept: %d", err);
break;
}
np = np_new(pl, newfd);
if (np == NULL) {
smbd_report("pipesvc_listener, alloc1 failed");
(void) close(newfd);
continue;
}
rc = pthread_create(&tid, NULL, pipesvc_worker, np);
if (rc != 0) {
smbd_report("pipesvc_listener, pthread_create: %d",
errno);
np_free(np);
continue;
}
(void) pthread_detach(tid);
/* Note: np_free in pipesvc_worker */
np = NULL;
}
out:
(void) close(listen_fd);
pl->tid = 0;
return (NULL);
}
static void *
pipesvc_worker(void *varg)
{
XDR xdrs;
smb_pipehdr_t phdr;
ndr_pipe_t *np = varg;
struct pipe_listener *pl = np->np_listener;
void *buf = NULL;
uint32_t status;
ssize_t rc;
(void) mutex_lock(&pipesvc_mutex);
if (pipesvc_workers_cur >= pipesvc_workers_max ||
pl->current >= pl->max_allowed) {
(void) mutex_unlock(&pipesvc_mutex);
status = NT_STATUS_PIPE_NOT_AVAILABLE;
(void) send(np->np_fid, &status, sizeof (status), 0);
goto out_free_np;
}
pipesvc_workers_cur++;
pl->current++;
if (pl->max_seen < pl->current)
pl->max_seen = pl->current;
(void) mutex_unlock(&pipesvc_mutex);
/*
* The smbsrv kmod sends us one initial message containing an
* XDR encoded smb_netuserinfo_t that we read and decode here,
* all unbeknownst to libmlrpc.
*
* Might be nice to enhance getpeerucred() so it can give us
* all the info smb_netuserinfo_t carries, and then use that,
* which would allow using a more generic RPC service.
*/
rc = pipe_recv(np, &phdr, sizeof (phdr));
if (rc != 0) {
smbd_report("pipesvc_worker, recv1: %d", rc);
goto out_decr;
}
if (phdr.ph_magic != SMB_PIPE_HDR_MAGIC ||
phdr.ph_uilen > 8192) {
smbd_report("pipesvc_worker, bad hdr");
goto out_decr;
}
buf = malloc(phdr.ph_uilen);
if (buf == NULL) {
smbd_report("pipesvc_worker, alloc1 failed");
goto out_decr;
}
rc = pipe_recv(np, buf, phdr.ph_uilen);
if (rc != 0) {
smbd_report("pipesvc_worker, recv2: %d", rc);
goto out_decr;
}
xdrmem_create(&xdrs, buf, phdr.ph_uilen, XDR_DECODE);
if (!smb_netuserinfo_xdr(&xdrs, np->np_user)) {
smbd_report("pipesvc_worker, bad uinfo");
goto out_free_buf;
}
/*
* Later, could disallow opens of some pipes by
* anonymous users, etc. For now, reply "OK".
*/
status = 0;
rc = pipe_send(np, &status, sizeof (status));
if (rc != 0) {
smbd_report("pipesvc_worker, send1: %d", rc);
goto out_free_buf;
}
/*
* Run the RPC service loop worker, which
* returns when it sees the pipe close.
*/
ndr_pipe_worker(np);
xdrs.x_op = XDR_FREE;
(void) smb_netuserinfo_xdr(&xdrs, np->np_user);
out_free_buf:
free(buf);
xdr_destroy(&xdrs);
out_decr:
(void) mutex_lock(&pipesvc_mutex);
pipesvc_workers_cur--;
pl->current--;
(void) mutex_unlock(&pipesvc_mutex);
out_free_np:
/* Cleanup what came in by varg. */
(void) shutdown(np->np_fid, SHUT_RDWR);
np_free(np);
return (NULL);
}
/*
* These are the transport get/put callback functions provided
* via the ndr_pipe_t object to the libmlrpc`ndr_pipe_worker.
* These are called only with known PDU sizes and should
* loop as needed to transfer the entire message.
*/
static int
pipe_recv(ndr_pipe_t *np, void *buf, size_t len)
{
int x;
while (len > 0) {
x = recv(np->np_fid, buf, len, 0);
if (x < 0)
return (errno);
if (x == 0)
return (EIO);
buf = (char *)buf + x;
len -= x;
}
return (0);
}
static int
pipe_send(ndr_pipe_t *np, void *buf, size_t len)
{
int x;
while (len > 0) {
x = send(np->np_fid, buf, len, 0);
if (x < 0)
return (errno);
if (x == 0)
return (EIO);
buf = (char *)buf + x;
len -= x;
}
return (0);
}