/* * Copyright 2011 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2000, Boris Popov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Boris Popov. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: rq.c,v 1.4 2004/12/13 00:25:23 lindak Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "private.h" #define MIN_REPLY_SIZE 4096 static uint32_t smb_map_doserr(uint8_t, uint16_t); /* * Create and initialize a request structure, for either an * "internal" request (one that does not use the driver) or * a regular "driver" request, that uses driver ioctls. * * The two kinds are built a little differently: * Driver requests are composed starting with the * first word of the "variable word vector" section. * The driver prepends the SMB header and word count. * The driver also needs an output buffer to receive * the response, filled in via copyout in the ioctl. * * Internal requests are composed entirely in this library. * Space for the SMB header is reserved here, and later * filled in by smb_rq_internal before the send/receive. */ int smb_rq_init(struct smb_ctx *ctx, uchar_t cmd, struct smb_rq **rqpp) { struct smb_rq *rqp; rqp = malloc(sizeof (*rqp)); if (rqp == NULL) goto errout; bzero(rqp, sizeof (*rqp)); rqp->rq_cmd = cmd; rqp->rq_ctx = ctx; /* * Setup the request buffer. * Do the reply buffer later. */ if (mb_init(&rqp->rq_rq)) goto errout; /* Space for the SMB header. (filled in later) */ mb_put_mem(&rqp->rq_rq, NULL, SMB_HDRLEN, MB_MSYSTEM); /* * Copy the ctx flags here, so the caller can * update the req flags before the OTW call. */ rqp->rq_hflags = ctx->ct_hflags; rqp->rq_hflags2 = ctx->ct_hflags2; *rqpp = rqp; return (0); errout: if (rqp) { smb_rq_done(rqp); free(rqp); } return (ENOMEM); } void smb_rq_done(struct smb_rq *rqp) { mb_done(&rqp->rq_rp); mb_done(&rqp->rq_rq); free(rqp); } /* * Reserve space for the word count, which is filled in later by * smb_rq_wend(). Also initialize the counter that it uses * to figure out what value to fill in. * * Note that the word count happens to be 8-bits, * which can lead to confusion. */ void smb_rq_wstart(struct smb_rq *rqp) { struct mbdata *mbp = &rqp->rq_rq; (void) mb_fit(mbp, 1, &rqp->rq_wcntp); rqp->rq_wcbase = mbp->mb_count; } /* * Fill in the word count, in the space reserved by * smb_rq_wstart(). */ void smb_rq_wend(struct smb_rq *rqp) { struct mbdata *mbp = &rqp->rq_rq; int wcnt; if (rqp->rq_wcntp == NULL) { DPRINT("no wcount ptr\n"); return; } wcnt = mbp->mb_count - rqp->rq_wcbase; if (wcnt > 0x1ff) DPRINT("word count too large (%d)\n", wcnt); if (wcnt & 1) DPRINT("odd word count\n"); wcnt >>= 1; /* * Fill in the word count (8-bits). * Also store it in the rq, in case * we're using the ioctl path. */ *rqp->rq_wcntp = (char)wcnt; } /* * Reserve space for the byte count, which is filled in later by * smb_rq_bend(). Also initialize the counter that it uses * to figure out what value to fill in. * * Note that the byte count happens to be 16-bits, * which can lead to confusion. */ void smb_rq_bstart(struct smb_rq *rqp) { struct mbdata *mbp = &rqp->rq_rq; (void) mb_fit(mbp, 2, &rqp->rq_bcntp); rqp->rq_bcbase = mbp->mb_count; } /* * Fill in the byte count, in the space reserved by * smb_rq_bstart(). */ void smb_rq_bend(struct smb_rq *rqp) { struct mbdata *mbp = &rqp->rq_rq; int bcnt; if (rqp->rq_bcntp == NULL) { DPRINT("no bcount ptr\n"); return; } bcnt = mbp->mb_count - rqp->rq_bcbase; if (bcnt > 0xffff) DPRINT("byte count too large (%d)\n", bcnt); /* * Fill in the byte count (16-bits). * Also store it in the rq, in case * we're using the ioctl path. * * The pointer is char * type due to * typical off-by-one alignment. */ rqp->rq_bcntp[0] = bcnt & 0xFF; rqp->rq_bcntp[1] = (bcnt >> 8); } int smb_rq_simple(struct smb_rq *rqp) { struct smbioc_rq krq; struct mbdata *mbp; mbuf_t *m; char *data; uint32_t len; size_t rpbufsz; int error; bzero(&krq, sizeof (krq)); krq.ioc_cmd = rqp->rq_cmd; /* * Make the SMB request body contiguous, * and fill in the ioctl request. */ mbp = smb_rq_getrequest(rqp); error = m_lineup(mbp->mb_top, &mbp->mb_top); if (error) return (error); data = mtod(mbp->mb_top, char *); len = m_totlen(mbp->mb_top); /* * _rq_init left space for the SMB header, * which makes mb_count the offset from * the beginning of the header (useful). * However, in this code path the driver * prepends the header, so we skip it. */ krq.ioc_tbufsz = len - SMB_HDRLEN; krq.ioc_tbuf = data + SMB_HDRLEN; /* * Setup a buffer to hold the reply, * at least MIN_REPLY_SIZE, or larger * if the caller increased rq_rpbufsz. */ mbp = smb_rq_getreply(rqp); rpbufsz = rqp->rq_rpbufsz; if (rpbufsz < MIN_REPLY_SIZE) rpbufsz = MIN_REPLY_SIZE; if ((error = m_get(rpbufsz, &m)) != 0) return (error); mb_initm(mbp, m); krq.ioc_rbufsz = rpbufsz; krq.ioc_rbuf = mtod(m, char *); /* * Call the driver */ if (ioctl(rqp->rq_ctx->ct_dev_fd, SMBIOC_REQUEST, &krq) == -1) return (errno); /* * Initialize returned mbdata. * SMB header already parsed. */ m->m_len = krq.ioc_rbufsz; return (0); } int smb_t2_request(int dev_fd, int setupcount, uint16_t *setup, const char *name, int tparamcnt, void *tparam, int tdatacnt, void *tdata, int *rparamcnt, void *rparam, int *rdatacnt, void *rdata, int *buffer_oflow) { smbioc_t2rq_t *krq; int i; krq = (smbioc_t2rq_t *)malloc(sizeof (smbioc_t2rq_t)); bzero(krq, sizeof (*krq)); if (setupcount < 0 || setupcount >= SMBIOC_T2RQ_MAXSETUP) { /* Bogus setup count, or too many setup words */ return (EINVAL); } for (i = 0; i < setupcount; i++) krq->ioc_setup[i] = setup[i]; krq->ioc_setupcnt = setupcount; strcpy(krq->ioc_name, name); krq->ioc_tparamcnt = tparamcnt; krq->ioc_tparam = tparam; krq->ioc_tdatacnt = tdatacnt; krq->ioc_tdata = tdata; krq->ioc_rparamcnt = *rparamcnt; krq->ioc_rdatacnt = *rdatacnt; krq->ioc_rparam = rparam; krq->ioc_rdata = rdata; if (ioctl(dev_fd, SMBIOC_T2RQ, krq) == -1) { return (errno); } *rparamcnt = krq->ioc_rparamcnt; *rdatacnt = krq->ioc_rdatacnt; *buffer_oflow = (krq->ioc_rpflags2 & SMB_FLAGS2_ERR_STATUS) && (krq->ioc_error == NT_STATUS_BUFFER_OVERFLOW); free(krq); return (0); } /* * Do an over-the-wire call without using the nsmb driver. * This is all "internal" to this library, and used only * for connection setup (negotiate protocol, etc.) */ int smb_rq_internal(struct smb_ctx *ctx, struct smb_rq *rqp) { static const uint8_t ffsmb[4] = SMB_SIGNATURE; struct smb_iods *is = &ctx->ct_iods; uint32_t sigbuf[2]; struct mbdata mbtmp, *mbp; int err, save_mlen; uint8_t ctmp; rqp->rq_uid = is->is_smbuid; rqp->rq_tid = SMB_TID_UNKNOWN; rqp->rq_mid = is->is_next_mid++; /* * Fill in the NBT and SMB headers * Using mbtmp so we can rewind without * affecting the passed request mbdata. */ bcopy(&rqp->rq_rq, &mbtmp, sizeof (mbtmp)); mbp = &mbtmp; mbp->mb_cur = mbp->mb_top; mbp->mb_pos = mbp->mb_cur->m_data; mbp->mb_count = 0; /* Have to save and restore m_len */ save_mlen = mbp->mb_cur->m_len; mbp->mb_cur->m_len = 0; /* * rewind done; fill it in */ mb_put_mem(mbp, ffsmb, SMB_SIGLEN, MB_MSYSTEM); mb_put_uint8(mbp, rqp->rq_cmd); mb_put_uint32le(mbp, 0); /* status */ mb_put_uint8(mbp, rqp->rq_hflags); mb_put_uint16le(mbp, rqp->rq_hflags2); /* pid_hi(2), signature(8), reserved(2) */ mb_put_mem(mbp, NULL, 12, MB_MZERO); mb_put_uint16le(mbp, rqp->rq_tid); mb_put_uint16le(mbp, 0); /* pid_lo */ mb_put_uint16le(mbp, rqp->rq_uid); mb_put_uint16le(mbp, rqp->rq_mid); /* Restore original m_len */ mbp->mb_cur->m_len = save_mlen; /* * Sign the message, if flags2 indicates. */ if (rqp->rq_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) { smb_rq_sign(rqp); } /* * Send it, wait for the reply. */ if ((err = smb_ssn_send(ctx, &rqp->rq_rq)) != 0) return (err); if ((err = smb_ssn_recv(ctx, &rqp->rq_rp)) != 0) return (err); /* * Should have an SMB header, at least. */ mbp = &rqp->rq_rp; if (mbp->mb_cur->m_len < SMB_HDRLEN) { DPRINT("len < 32"); return (EBADRPC); } /* * If the request was signed, validate the * signature on the response. */ if (rqp->rq_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) { err = smb_rq_verify(rqp); if (err) { DPRINT("bad signature"); return (err); } } /* * Decode the SMB header. */ md_get_mem(mbp, (char *)sigbuf, 4, MB_MSYSTEM); if (0 != bcmp(sigbuf, ffsmb, 4)) { DPRINT("not SMB"); return (EBADRPC); } md_get_uint8(mbp, &ctmp); /* SMB cmd */ md_get_uint32le(mbp, &rqp->rq_status); md_get_uint8(mbp, &rqp->rq_hflags); md_get_uint16le(mbp, &rqp->rq_hflags2); /* pid_hi(2), signature(8), reserved(2) */ md_get_mem(mbp, NULL, 12, MB_MSYSTEM); md_get_uint16le(mbp, &rqp->rq_tid); md_get_uint16le(mbp, NULL); /* pid_lo */ md_get_uint16le(mbp, &rqp->rq_uid); md_get_uint16le(mbp, &rqp->rq_mid); /* * Figure out the status return. * Caller looks at rq_status. */ if ((rqp->rq_hflags2 & SMB_FLAGS2_ERR_STATUS) == 0) { uint16_t serr; uint8_t class; class = rqp->rq_status & 0xff; serr = rqp->rq_status >> 16; rqp->rq_status = smb_map_doserr(class, serr); } return (0); } /* * Map old DOS errors (etc.) to NT status codes. * We probably don't need this anymore, since * the oldest server we talk to is NT. But if * later find we do need this, add support here * for the DOS errors we care about. */ static uint32_t smb_map_doserr(uint8_t class, uint16_t serr) { if (class == 0 && serr == 0) return (0); DPRINT("class 0x%x serr 0x%x", (int)class, (int)serr); return (NT_STATUS_UNSUCCESSFUL); }