#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 <ucred.h>
#include <priv.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;
#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;
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);
}
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);
(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:
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);
smbd_nomem();
}
rc = pthread_create(&tid, NULL, pipesvc_worker, np);
if (rc != 0) {
smbd_report("pipesvc_listener, pthread_create: %d",
errno);
np_free(np);
smbd_nomem();
}
(void) pthread_detach(tid);
np = NULL;
}
out:
(void) close(listen_fd);
pl->tid = 0;
return (NULL);
}
#ifndef FKSMBD
static boolean_t
pipe_has_priv(ndr_pipe_t *np)
{
ucred_t *uc = NULL;
const priv_set_t *ps = NULL;
boolean_t ret = B_FALSE;
pid_t clpid;
if (getpeerucred(np->np_fid, &uc) != 0) {
smbd_report("pipesvc: getpeerucred err %d", errno);
return (B_FALSE);
}
clpid = ucred_getpid(uc);
ps = ucred_getprivset(uc, PRIV_PERMITTED);
if (ps == NULL) {
smbd_report("pipesvc: ucred_getprivset failed");
goto out;
}
if (priv_ismember(ps, PRIV_SYS_SMB)) {
ret = B_TRUE;
goto out;
}
if (smbd.s_debug) {
smbd_report("pipesvc: non-privileged client "
"PID = %d UID = %d",
(int)clpid, ucred_getruid(uc));
}
out:
if (uc != NULL)
ucred_free(uc);
return (ret);
}
#endif
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);
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;
}
#ifndef FKSMBD
if (!pipe_has_priv(np)) {
np->np_user->ui_flags = SMB_ATF_ANON;
}
#endif
status = 0;
rc = pipe_send(np, &status, sizeof (status));
if (rc != 0) {
smbd_report("pipesvc_worker, send1: %d", rc);
goto out_free_buf;
}
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:
(void) shutdown(np->np_fid, SHUT_RDWR);
np_free(np);
return (NULL);
}
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);
}