1/*-
2 * Copyright (c) 1999-2001 Boris Popov
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * Routines to prepare request and fetch reply
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/errno.h>
35#include <sys/kernel.h>
36#include <sys/malloc.h>
37#include <sys/mbuf.h>
38#include <sys/poll.h>
39#include <sys/proc.h>
40#include <sys/socket.h>
41#include <sys/socketvar.h>
42#include <sys/uio.h>
43
44#include <netncp/ncp.h>
45#include <netncp/ncp_conn.h>
46#include <netncp/ncp_rq.h>
47#include <netncp/ncp_subr.h>
48#include <netncp/ncp_ncp.h>
49#include <netncp/ncp_sock.h>
50#include <netncp/ncp_nls.h>
51
52static MALLOC_DEFINE(M_NCPRQ, "NCPRQ", "NCP request");
53
54static int ncp_sign_packet(struct ncp_conn *conn, struct ncp_rq *rqp, int *size);
55
56int
57ncp_rq_alloc_any(u_int32_t ptype, u_int8_t fn, struct ncp_conn *ncp,
58	struct thread *td, struct ucred *cred,
59	struct ncp_rq **rqpp)
60{
61	struct ncp_rq *rqp;
62	int error;
63
64	rqp = malloc(sizeof(*rqp), M_NCPRQ, M_WAITOK);
65	error = ncp_rq_init_any(rqp, ptype, fn, ncp, td, cred);
66	rqp->nr_flags |= NCPR_ALLOCED;
67	if (error) {
68		ncp_rq_done(rqp);
69		return error;
70	}
71	*rqpp = rqp;
72	return 0;
73}
74
75int
76ncp_rq_alloc(u_int8_t fn, struct ncp_conn *ncp,
77	struct thread *td, struct ucred *cred, struct ncp_rq **rqpp)
78{
79	return ncp_rq_alloc_any(NCP_REQUEST, fn, ncp, td, cred, rqpp);
80}
81
82int
83ncp_rq_alloc_subfn(u_int8_t fn, u_int8_t subfn, struct ncp_conn *ncp,
84	struct thread *td, struct ucred *cred, struct ncp_rq **rqpp)
85{
86	struct ncp_rq *rqp;
87	int error;
88
89	error = ncp_rq_alloc_any(NCP_REQUEST, fn, ncp, td, cred, &rqp);
90	if (error)
91		return error;
92	mb_reserve(&rqp->rq, 2);
93	mb_put_uint8(&rqp->rq, subfn);
94	*rqpp = rqp;
95	return 0;
96}
97
98int
99ncp_rq_init_any(struct ncp_rq *rqp, u_int32_t ptype, u_int8_t fn,
100	struct ncp_conn *ncp,
101	struct thread *td, struct ucred *cred)
102{
103	struct ncp_rqhdr *rq;
104	struct ncp_bursthdr *brq;
105	struct mbchain *mbp;
106	int error;
107
108	bzero(rqp, sizeof(*rqp));
109	error = ncp_conn_access(ncp, cred, NCPM_EXECUTE);
110	if (error)
111		return error;
112	rqp->nr_td = td;
113	rqp->nr_cred = cred;
114	rqp->nr_conn = ncp;
115	mbp = &rqp->rq;
116	if (mb_init(mbp) != 0)
117		return ENOBUFS;
118	switch(ptype) {
119	    case NCP_PACKET_BURST:
120		brq = (struct ncp_bursthdr*)mb_reserve(mbp, sizeof(*brq));
121		brq->bh_type = ptype;
122		brq->bh_streamtype = 0x2;
123		break;
124	    default:
125		rq = (struct ncp_rqhdr*)mb_reserve(mbp, sizeof(*rq));
126		rq->type = ptype;
127		rq->seq = 0;	/* filled later */
128		rq->fn = fn;
129		break;
130	}
131	rqp->nr_minrplen = -1;
132	return 0;
133}
134
135void
136ncp_rq_done(struct ncp_rq *rqp)
137{
138	mb_done(&rqp->rq);
139	md_done(&rqp->rp);
140	if (rqp->nr_flags & NCPR_ALLOCED)
141		free(rqp, M_NCPRQ);
142	return;
143}
144
145/*
146 * Routines to fill the request
147 */
148
149static int
150ncp_rq_pathstrhelp(struct mbchain *mbp, c_caddr_t src, caddr_t dst,
151    size_t *srclen, size_t *dstlen)
152{
153	int len;
154
155	if (*srclen < *dstlen) {
156		*dstlen = *srclen;
157		len = (int)*srclen;
158	} else {
159		*srclen = *dstlen;
160		len = (int)*dstlen;
161	}
162	ncp_pathcopy(src, dst, len, mbp->mb_udata);
163	return 0;
164}
165
166int
167ncp_rq_pathstring(struct ncp_rq *rqp, int size, const char *name,
168	struct ncp_nlstables *nt)
169{
170	struct mbchain *mbp = &rqp->rq;
171
172	mb_put_uint8(mbp, size);
173	mbp->mb_copy = ncp_rq_pathstrhelp;
174	mbp->mb_udata = nt;
175	return mb_put_mem(mbp, (c_caddr_t)name, size, MB_MCUSTOM);
176}
177
178int
179ncp_rq_pstring(struct ncp_rq *rqp, const char *s)
180{
181	u_int len = strlen(s);
182	int error;
183
184	if (len > 255)
185		return EINVAL;
186	error = mb_put_uint8(&rqp->rq, len);
187	if (error)
188		return error;
189	return mb_put_mem(&rqp->rq, s, len, MB_MSYSTEM);
190}
191
192int
193ncp_rq_dbase_path(struct ncp_rq *rqp, u_int8_t vol_num, u_int32_t dir_base,
194                    int namelen, u_char *path, struct ncp_nlstables *nt)
195{
196	struct mbchain *mbp = &rqp->rq;
197	int complen;
198
199	mb_put_uint8(mbp, vol_num);
200	mb_put_mem(mbp, (c_caddr_t)&dir_base, sizeof(dir_base), MB_MSYSTEM);
201	mb_put_uint8(mbp, 1);	/* with dirbase */
202	if (path != NULL && path[0]) {
203		if (namelen < 0) {
204			namelen = *path++;
205			mb_put_uint8(mbp, namelen);
206			for(; namelen; namelen--) {
207				complen = *path++;
208				mb_put_uint8(mbp, complen);
209				mb_put_mem(mbp, path, complen, MB_MSYSTEM);
210				path += complen;
211			}
212		} else {
213			mb_put_uint8(mbp, 1);	/* 1 component */
214			ncp_rq_pathstring(rqp, namelen, path, nt);
215		}
216	} else {
217		mb_put_uint8(mbp, 0);
218		mb_put_uint8(mbp, 0);
219	}
220	return 0;
221}
222
223/*
224 * Make a signature for the current packet and add it at the end of the
225 * packet.
226 */
227static int
228ncp_sign_packet(struct ncp_conn *conn, struct ncp_rq *rqp, int *size)
229{
230	u_char data[64];
231	int error;
232
233	bzero(data, sizeof(data));
234	bcopy(conn->sign_root, data, 8);
235	setdle(data, 8, *size);
236	m_copydata(rqp->rq.mb_top, sizeof(struct ncp_rqhdr) - 1,
237		min((*size) - sizeof(struct ncp_rqhdr)+1, 52), data + 12);
238	ncp_sign(conn->sign_state, data, conn->sign_state);
239	error = mb_put_mem(&rqp->rq, (caddr_t)conn->sign_state, 8, MB_MSYSTEM);
240	if (error)
241		return error;
242	(*size) += 8;
243	return 0;
244}
245
246/*
247 * Low level send rpc, here we do not attempt to restore any connection,
248 * Connection expected to be locked
249 */
250int
251ncp_request_int(struct ncp_rq *rqp)
252{
253	struct ncp_conn *conn = rqp->nr_conn;
254	struct thread *td = conn->td;
255	struct socket *so = conn->ncp_so;
256	struct ncp_rqhdr *rq;
257	struct ncp_rphdr *rp=NULL;
258	struct timeval tv;
259	struct mbuf *m, *mreply = NULL;
260	struct mbchain *mbp;
261	int error, len, dosend, plen = 0, gotpacket;
262
263	if (so == NULL) {
264		printf("%s: ncp_so is NULL !\n",__func__);
265		ncp_conn_invalidate(conn);
266		return ENOTCONN;
267	}
268	if (td == NULL)
269		td = curthread;	/* XXX maybe procpage ? */
270	/*
271	 * Flush out replies on previous reqs
272	 */
273	tv.tv_sec = 0;
274	tv.tv_usec = 0;
275	while (selsocket(so, POLLIN, &tv, td) == 0) {
276		if (ncp_sock_recv(so, &m, &len) != 0)
277			break;
278		m_freem(m);
279	}
280	mbp = &rqp->rq;
281	len = mb_fixhdr(mbp);
282	rq = mtod(mbp->mb_top, struct ncp_rqhdr *);
283	rq->seq = conn->seq;
284	m = rqp->rq.mb_top;
285
286	switch (rq->fn) {
287	    case 0x15: case 0x16: case 0x17: case 0x23:
288		*(u_int16_t*)(rq + 1) = htons(len - 2 - sizeof(*rq));
289		break;
290	}
291	if (conn->flags & NCPFL_SIGNACTIVE) {
292		error = ncp_sign_packet(conn, rqp, &len);
293		if (error)
294			return error;
295		mbp->mb_top->m_pkthdr.len = len;
296	}
297	rq->conn_low = conn->connid & 0xff;
298	/* rq->task = p->p_pgrp->pg_id & 0xff; */ /*p->p_pid*/
299	/* XXX: this is temporary fix till I find a better solution */
300	rq->task = rq->conn_low;
301	rq->conn_high = conn->connid >> 8;
302	rqp->rexmit = conn->li.retry_count;
303	error = 0;
304	for(dosend = 1;;) {
305		if (rqp->rexmit-- == 0) {
306			error = ETIMEDOUT;
307			break;
308		}
309		error = 0;
310		if (dosend) {
311			NCPSDEBUG("send:%04x f=%02x c=%d l=%d s=%d t=%d\n",rq->type, rq->fn, (rq->conn_high << 8) + rq->conn_low,
312				mbp->mb_top->m_pkthdr.len, rq->seq, rq->task
313			);
314			error = ncp_sock_send(so, mbp->mb_top, rqp);
315			if (error)
316				break;
317		}
318		tv.tv_sec = conn->li.timeout;
319		tv.tv_usec = 0;
320		error = selsocket(so, POLLIN, &tv, td);
321		if (error == EWOULDBLOCK )	/* timeout expired */
322			continue;
323		error = ncp_chkintr(conn, td);
324		if (error)
325			break;
326		/*
327		 * At this point it is possible to get more than one
328		 * reply from server. In general, last reply should be for
329		 * current request, but not always. So, we loop through
330		 * all replies to find the right answer and flush others.
331		 */
332		gotpacket = 0;	/* nothing good found */
333		dosend = 1;	/* resend rq if error */
334		for (;;) {
335			error = 0;
336			tv.tv_sec = 0;
337			tv.tv_usec = 0;
338			if (selsocket(so, POLLIN, &tv, td) != 0)
339				break;
340/*			if (so->so_rcv.sb_cc == 0) {
341				break;
342			}*/
343			error = ncp_sock_recv(so, &m, &len);
344			if (error)
345				break; 		/* must be more checks !!! */
346			if (m->m_len < sizeof(*rp)) {
347				m = m_pullup(m, sizeof(*rp));
348				if (m == NULL) {
349					printf("%s: reply too short\n",__func__);
350					continue;
351				}
352			}
353			rp = mtod(m, struct ncp_rphdr*);
354			if (len == sizeof(*rp) && rp->type == NCP_POSITIVE_ACK) {
355				NCPSDEBUG("got positive acknowledge\n");
356				m_freem(m);
357				rqp->rexmit = conn->li.retry_count;
358				dosend = 0;	/* server just busy and will reply ASAP */
359				continue;
360			}
361			NCPSDEBUG("recv:%04x c=%d l=%d s=%d t=%d cc=%02x cs=%02x\n",rp->type,
362			    (rp->conn_high << 8) + rp->conn_low, len, rp->seq, rp->task,
363			     rp->completion_code, rp->connection_state);
364			NCPDDEBUG(m);
365			if ( (rp->type == NCP_REPLY) &&
366			    ((rq->type == NCP_ALLOC_SLOT) ||
367			    ((rp->conn_low == rq->conn_low) &&
368			     (rp->conn_high == rq->conn_high)
369			    ))) {
370				if (rq->seq > rp->seq || (rq->seq == 0 && rp->seq == 0xff)) {
371					dosend = 1;
372				}
373				if (rp->seq == rq->seq) {
374					if (gotpacket) {
375						m_freem(m);
376					} else {
377						gotpacket = 1;
378						mreply = m;
379						plen = len;
380					}
381					continue;	/* look up other for other packets */
382				}
383			}
384			m_freem(m);
385			NCPSDEBUG("reply mismatch\n");
386		} /* for receive */
387		if (error || gotpacket)
388			break;
389		/* try to resend, or just wait */
390	}
391	conn->seq++;
392	if (error) {
393		NCPSDEBUG("error=%d\n", error);
394		/*
395		 * Any error except interruped call means that we have
396		 * to reconnect. So, eliminate future timeouts by invalidating
397		 * connection now.
398		 */
399		if (error != EINTR)
400			ncp_conn_invalidate(conn);
401		return (error);
402	}
403	if (conn->flags & NCPFL_SIGNACTIVE) {
404		/* XXX: check reply signature */
405		m_adj(mreply, -8);
406		plen -= 8;
407	}
408	rp = mtod(mreply, struct ncp_rphdr*);
409	md_initm(&rqp->rp, mreply);
410	rqp->nr_rpsize = plen - sizeof(*rp);
411	rqp->nr_cc = error = rp->completion_code;
412	if (error)
413		error |= 0x8900;	/* server error */
414	rqp->nr_cs = rp->connection_state;
415	if (rqp->nr_cs & (NCP_CS_BAD_CONN | NCP_CS_SERVER_DOWN)) {
416		NCPSDEBUG("server drop us\n");
417		ncp_conn_invalidate(conn);
418		error = ECONNRESET;
419	}
420	md_get_mem(&rqp->rp, NULL, sizeof(*rp), MB_MSYSTEM);
421	return error;
422}
423
424/*
425 * Here we will try to restore any loggedin & dropped connection,
426 * connection should be locked on entry
427 */
428static __inline int
429ncp_restore_login(struct ncp_conn *conn)
430{
431	int error;
432
433	printf("ncprq: Restoring connection, flags = %x\n", conn->flags);
434	conn->flags |= NCPFL_RESTORING;
435	error = ncp_conn_reconnect(conn);
436	if (!error && (conn->flags & NCPFL_WASLOGGED))
437		error = ncp_conn_login(conn, conn->td, conn->ucred);
438	if (error)
439		ncp_ncp_disconnect(conn);
440	conn->flags &= ~NCPFL_RESTORING;
441	return error;
442}
443
444int
445ncp_request(struct ncp_rq *rqp)
446{
447	struct ncp_conn *ncp = rqp->nr_conn;
448	int error, rcnt;
449
450	error = ncp_conn_lock(ncp, rqp->nr_td, rqp->nr_cred, NCPM_EXECUTE);
451	if (error)
452		goto out;
453	rcnt = NCP_RESTORE_COUNT;
454	for(;;) {
455		if (ncp->flags & NCPFL_ATTACHED) {
456			error = ncp_request_int(rqp);
457			if (ncp->flags & NCPFL_ATTACHED)
458				break;
459		}
460		if (rcnt-- == 0) {
461			error = ECONNRESET;
462			break;
463		}
464		/*
465		 * Do not attempt to restore connection recursively
466		 */
467		if (ncp->flags & NCPFL_RESTORING) {
468			error = ENOTCONN;
469			break;
470		}
471		error = ncp_restore_login(ncp);
472		if (error)
473			continue;
474	}
475	ncp_conn_unlock(ncp, rqp->nr_td);
476out:
477	if (error && (rqp->nr_flags & NCPR_DONTFREEONERR) == 0)
478		ncp_rq_done(rqp);
479	return error;
480}
481