route.8: Remove NS remains.
[dragonfly.git] / sys / netproto / ncp / ncp_ncp.c
1 /*
2  * Copyright (c) 1999, 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  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *    This product includes software developed by Boris Popov.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $FreeBSD: src/sys/netncp/ncp_ncp.c,v 1.3 1999/10/29 10:21:07 bp Exp $
33  *
34  * Core of NCP protocol
35  */
36 #include "opt_inet.h"
37 #include "opt_ipx.h"
38
39 #include <sys/param.h>
40 #include <sys/errno.h>
41 #include <sys/systm.h>
42 #include <sys/proc.h>
43 #include <sys/priv.h>
44 #include <sys/poll.h>
45 #include <sys/signalvar.h>
46 #include <sys/signal2.h>
47 #include <sys/mbuf.h>
48 #include <sys/thread2.h>
49 #include <sys/socketvar.h>
50
51 #ifdef IPX
52 #include <netproto/ipx/ipx.h>
53 #include <netproto/ipx/ipx_var.h>
54 #endif
55
56 #include "ncp.h"
57 #include "ncp_conn.h"
58 #include "ncp_sock.h"
59 #include "ncp_subr.h"
60 #include "ncp_ncp.h"
61 #include "ncp_rq.h"
62 #include "nwerror.h"
63
64 static int ncp_do_request(struct ncp_conn *,struct ncp_rq *rqp);
65 static int ncp_negotiate_buffersize(struct ncp_conn *conn, int size, int *target);
66 static int ncp_renegotiate_connparam(struct ncp_conn *conn, int buffsize, int in_options);
67 static void ncp_sign_packet(struct ncp_conn *conn, struct ncp_rq *rqp, int *size);
68
69
70 #ifdef NCP_DATA_DEBUG
71 static void
72 m_dumpm(struct mbuf *m)
73 {
74         char *p;
75         int len;
76         kprintf("d=");
77         while(m) {
78                 p=mtod(m,char *);
79                 len=m->m_len;
80                 kprintf("(%d)",len);
81                 while(len--){
82                         kprintf("%02x ",((int)*(p++)) & 0xff);
83                 }
84                 m=m->m_next;
85         }
86         kprintf("\n");
87 }
88 #endif /* NCP_DATA_DEBUG */
89
90 int
91 ncp_chkintr(struct ncp_conn *conn, struct thread *td)
92 {
93         sigset_t tmpset;
94         struct lwp *lp = td->td_lwp;
95         struct proc *p = td->td_proc;
96
97         if (p == NULL)
98                 return 0;
99         tmpset = lwp_sigpend(lp);
100         SIGSETNAND(tmpset, lp->lwp_sigmask);
101         SIGSETNAND(tmpset, p->p_sigignore);
102         if (SIGNOTEMPTY(tmpset) && NCP_SIGMASK(tmpset))
103                 return EINTR;
104         return 0;
105 }
106
107 /*
108  * Process initial NCP handshake (attach)
109  * NOTE: Since all functions below may change conn attributes, they
110  * should be called with LOCKED connection, also they use td & ucred
111  */
112 int
113 ncp_ncp_connect(struct ncp_conn *conn) {
114         int error;
115         struct ncp_rphdr *rp;
116         DECLARE_RQ;
117         
118         conn->flags &= ~(NCPFL_INVALID | NCPFL_SIGNACTIVE | NCPFL_SIGNWANTED);
119         conn->seq = 0;
120         checkbad(ncp_rq_head(rqp,NCP_ALLOC_SLOT,0,conn->td,conn->ucred));
121         error=ncp_do_request(conn,rqp);
122         if (!error) {
123                 rp = mtod(rqp->rp, struct ncp_rphdr*);
124                 conn->connid = rp->conn_low + (rp->conn_high << 8);
125         }
126         ncp_rq_done(rqp);
127         if (error) return error;
128         conn->flags |= NCPFL_ATTACHED;
129
130         error = ncp_renegotiate_connparam(conn, NCP_DEFAULT_BUFSIZE, 0);
131         if (error == NWE_SIGNATURE_LEVEL_CONFLICT) {
132                 kprintf("Unable to negotiate requested security level\n");
133                 error = EOPNOTSUPP;
134         }
135         if (error) {
136                 ncp_ncp_disconnect(conn);
137                 return error;
138         }
139 #ifdef NCPBURST
140         ncp_burst_connect(conn);
141 #endif
142 bad:
143         return error;
144 }
145
146 int
147 ncp_ncp_disconnect(struct ncp_conn *conn) {
148         int error;
149         DECLARE_RQ;
150
151         NCPSDEBUG("for connid=%d\n",conn->nc_id);
152 #ifdef NCPBURST
153         ncp_burst_disconnect(conn);
154 #endif
155         error=ncp_rq_head(rqp,NCP_FREE_SLOT,0,conn->td,conn->ucred);
156         error=ncp_do_request(conn,rqp);
157         ncp_rq_done(rqp);
158         ncp_conn_invalidate(conn);
159         ncp_sock_disconnect(conn);
160         return 0;
161 }
162 /* 
163  * Make a signature for the current packet and add it at the end of the
164  * packet.
165  */
166 static void
167 ncp_sign_packet(struct ncp_conn *conn, struct ncp_rq *rqp, int *size) {
168         u_char data[64];
169
170         bzero(data, sizeof(data));
171         bcopy(conn->sign_root, data, 8);
172         setdle(data, 8, *size);
173         m_copydata(rqp->rq, sizeof(struct ncp_rqhdr)-1,
174                 min((*size) - sizeof(struct ncp_rqhdr)+1, 52),data+12);
175         ncp_sign(conn->sign_state, data, conn->sign_state);
176         ncp_rq_mem(rqp, (void*)conn->sign_state, 8);
177         (*size) += 8;
178 }
179
180 /*
181  * Low level send rpc, here we do not attempt to restore any connection,
182  * Connection expected to be locked
183  */
184 static int 
185 ncp_do_request(struct ncp_conn *conn, struct ncp_rq *rqp) {
186         int error=EIO,len, dosend, plen = 0, gotpacket;
187         struct socket *so;
188         struct thread *td = conn->td;
189         struct ncp_rqhdr *rq;
190         struct ncp_rphdr *rp=NULL;
191         struct timespec ts;
192         struct mbuf *m, *mreply = NULL;
193         int res;
194         
195         conn->nc_rq = rqp;
196         rqp->conn = conn;
197         if (td == NULL)
198                 td = curthread; /* XXX maybe procpage ? */
199         if (!ncp_conn_valid(conn)) {
200                 kprintf("%s: conn not valid\n",__func__);
201                 return (error);
202         }
203         so = conn->ncp_so;
204         if (!so) {
205                 kprintf("%s: ncp_so is NULL !\n",__func__);
206                 ncp_conn_invalidate(conn);      /* wow ! how we do that ? */
207                 return EBADF;
208         }
209         /*
210          * Flush out replies on previous reqs
211          */
212         crit_enter();
213         while (1/*so->so_rcv.sb_cc*/) {
214                 struct sockbuf sio;
215                 ts.tv_sec = 0;
216                 ts.tv_nsec = 0;
217
218                 error = socket_wait(so, &ts, &res);
219                 if (error || !res)
220                         break;
221                 if (ncp_sock_recv(so, &sio) != 0)       
222                         break;
223                 sbflush(&sio);
224         }
225         rq = mtod(rqp->rq,struct ncp_rqhdr *);
226         rq->seq = conn->seq;
227         m = rqp->rq;
228         len = 0;
229         while (m) {
230                 len += m->m_len;
231                 m = m->m_next;
232         }
233         rqp->rq->m_pkthdr.len = len;
234         switch(rq->fn) {
235             case 0x15: case 0x16: case 0x17: case 0x23:
236                 m = rqp->rq;
237                 *((u_int16_t*)(mtod(m,u_int8_t*)+sizeof(*rq))) = htons(len-2-sizeof(*rq));
238                 break;
239         }
240         if (conn->flags & NCPFL_SIGNACTIVE) {
241                 ncp_sign_packet(conn, rqp, &len);
242                 rqp->rq->m_pkthdr.len = len;
243         }
244         rq->conn_low = conn->connid & 0xff;
245         /* rq->task = p->p_pgrp->pg_id & 0xff; */ /*p->p_pid*/
246         /* XXX: this is temporary fix till I find a better solution */
247         rq->task = rq->conn_low;
248         rq->conn_high = conn->connid >> 8;
249         rqp->rexmit = conn->li.retry_count;
250         for(dosend = 1;;) {
251                 if (rqp->rexmit-- == 0) {
252                         error = ETIMEDOUT;
253                         break;
254                 }
255                 error = 0;
256                 if (dosend) {
257                         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,
258                                 rqp->rq->m_pkthdr.len, rq->seq, rq->task
259                         );
260                         error = ncp_sock_send(so, rqp->rq, rqp);
261                         if (error) break;
262                 }
263                 ts.tv_sec = conn->li.timeout;
264                 ts.tv_nsec = 0;
265                 error = socket_wait(so, &ts, &res);
266                 if (error == EWOULDBLOCK )      /* timeout expired */
267                         continue;
268                 error = ncp_chkintr(conn, td);
269                 if (error == EINTR)             /* we dont restart */
270                         break;
271                 if (error || !res) break;
272                 /*
273                  * At this point it is possible to get more than one
274                  * reply from server. In general, last reply should be for
275                  * current request, but not always. So, we loop through
276                  * all replies to find the right answer and flush others.
277                  */
278                 gotpacket = 0;  /* nothing good found */
279                 dosend = 1;     /* resend rq if error */
280                 for (;;) {
281                         struct sockbuf sio;
282
283                         error = 0;
284                         ts.tv_sec = 0;
285                         ts.tv_nsec = 0;
286                         error = socket_wait(so, &ts, &res);
287                         if (error || !res)
288                                 break;
289                         error = ncp_sock_recv(so, &sio);
290                         if (error) break;               /* must be more checks !!! */
291
292                         m = sio.sb_mb;
293                         if (m->m_len < sizeof(*rp)) {
294                                 m = m_pullup(m, sizeof(*rp));
295                                 if (m == NULL) {
296                                         kprintf("%s: reply too short\n",__func__);
297                                         continue;
298                                 }
299                         }
300                         rp = mtod(m, struct ncp_rphdr*);
301                         if (sio.sb_cc == sizeof(*rp) && rp->type == NCP_POSITIVE_ACK) {
302                                 NCPSDEBUG("got positive acknowledge\n");
303                                 m_freem(m);
304                                 rqp->rexmit = conn->li.retry_count;
305                                 dosend = 0;     /* server just busy and will reply ASAP */
306                                 continue;
307                         }
308                         NCPSDEBUG("recv:%04x c=%d l=%d s=%d t=%d cc=%02x cs=%02x\n",rp->type,
309                             (rp->conn_high << 8) + rp->conn_low, sio.len, rp->seq, rp->task,
310                              rp->completion_code, rp->connection_state);
311                         NCPDDEBUG(m);
312                         if ( (rp->type == NCP_REPLY) && 
313                             ((rq->type == NCP_ALLOC_SLOT) || 
314                             ((rp->conn_low == rq->conn_low) &&
315                              (rp->conn_high == rq->conn_high)
316                             ))) {
317                                 if (rq->seq > rp->seq || (rq->seq == 0 && rp->seq == 0xff)) {
318                                         dosend = 1;
319                                 }
320                                 if (rp->seq == rq->seq) {
321                                         if (gotpacket) {
322                                                 m_freem(m);
323                                         } else {
324                                                 gotpacket = 1;
325                                                 mreply = m;
326                                                 plen = sio.sb_cc;
327                                         }
328                                         continue;       /* look up other for other packets */
329                                 }
330                         }
331                         m_freem(m);
332                         NCPSDEBUG("reply mismatch\n");
333                 } /* for receive */
334                 if (error) break;
335                 if (gotpacket) break;
336                 /* try to resend, or just wait */
337         }
338         crit_exit();
339         conn->seq++;
340         if (error) {
341                 NCPSDEBUG("error=%d\n",error);
342                 if (error != EINTR)                     /* if not just interrupt */
343                         ncp_conn_invalidate(conn);      /* only reconnect to restore */
344                 return(error);
345         }
346         if (conn->flags & NCPFL_SIGNACTIVE) {
347                 /* XXX: check reply signature */
348                 m_adj(mreply, -8);
349                 plen -= 8;
350         }
351         len = plen;
352         m = mreply;
353         rp = mtod(m, struct ncp_rphdr*);
354         len -= sizeof(*rp);
355         rqp->rpsize = len;
356         rqp->cc = error = rp->completion_code;
357         if (error) error |= 0x8900;     /* server error */
358         rqp->cs = rp->connection_state;
359         if (rqp->cs & (NCP_CS_BAD_CONN | NCP_CS_SERVER_DOWN)) {
360                 NCPSDEBUG("server drop us\n");
361                 ncp_conn_invalidate(conn);
362                 error = ECONNRESET;
363         }
364         rqp->rp = m;
365         rqp->mrp = m;
366         rqp->bpos = mtod(m, caddr_t) + sizeof(*rp);
367         return error;
368 }
369
370 /*
371  * Here we will try to restore any loggedin & dropped connection,
372  * connection should be locked on entry
373  */
374 int ncp_restore_login(struct ncp_conn *conn);
375 int
376 ncp_restore_login(struct ncp_conn *conn) {
377         int error, oldflags;
378
379         if (conn->flags & NCPFL_RESTORING) {
380                 kprintf("Hey, ncp_restore_login called twise !!!\n");
381                 return 0;
382         }
383         oldflags = conn->flags;
384         kprintf("Restoring connection, flags = %d\n",oldflags);
385         if ((oldflags & NCPFL_LOGGED) == 0) {
386                 return ECONNRESET;      /* no need to restore empty conn */
387         }
388         conn->flags &= ~(NCPFL_LOGGED | NCPFL_ATTACHED);
389         conn->flags |= NCPFL_RESTORING;
390         do {    /* not a loop */
391                 error = ncp_reconnect(conn);
392                 if (error) break;
393                 if (conn->li.user)
394                         error = ncp_login_object(conn, conn->li.user, conn->li.objtype, conn->li.password,conn->td,conn->ucred);
395                 if (error) break;
396                 conn->flags |= NCPFL_LOGGED;
397         } while(0);
398         if (error) {
399                 conn->flags = oldflags | NCPFL_INVALID;
400         }
401         conn->flags &= ~NCPFL_RESTORING;
402         return error;
403 }
404
405 int
406 ncp_request(struct ncp_conn *conn, struct ncp_rq *rqp) {
407         int error, rcnt;
408 /*      struct ncp_rqhdr *rq = mtod(rqp->rq,struct ncp_rqhdr*);*/
409
410         error = ncp_conn_lock(conn,rqp->td,rqp->cred,NCPM_EXECUTE);
411         if  (error) return error;
412         rcnt = NCP_RESTORE_COUNT;
413         for(;;) {
414                 if (!ncp_conn_valid(conn)) {
415                         if (rcnt==0) {
416                                 error = ECONNRESET;
417                                 break;
418                         }
419                         rcnt--;
420                         error = ncp_restore_login(conn);
421                         if (error)
422                                 continue;
423                 }
424                 error=ncp_do_request(conn, rqp);
425                 if (ncp_conn_valid(conn))       /* not just error ! */
426                         break;
427         }
428         ncp_conn_unlock(conn,rqp->td);
429         return error;
430 }
431
432 /*
433  * All negotiation functions expect a locked connection
434  */
435 static int
436 ncp_negotiate_buffersize(struct ncp_conn *conn, int size, int *target) {
437         int error;
438         DECLARE_RQ;
439
440         NCP_RQ_HEAD(0x21,conn->td,conn->ucred);
441         ncp_rq_word_hl(rqp, size);
442         checkbad(ncp_request(conn,rqp));
443         *target = min(ncp_rp_word_hl(rqp), size);
444         NCP_RQ_EXIT;
445         return error;
446 }
447
448 static int
449 ncp_negotiate_size_and_options(struct ncp_conn *conn, int size, int options, 
450             int *ret_size, int *ret_options) {
451         int error;
452         int rs;
453         DECLARE_RQ;
454
455         NCP_RQ_HEAD(0x61,conn->td,conn->ucred);
456         ncp_rq_word_hl(rqp, size);
457         ncp_rq_byte(rqp, options);
458         checkbad(ncp_request(conn, rqp));
459         rs = ncp_rp_word_hl(rqp);
460         *ret_size = (rs == 0) ? size : min(rs, size);
461         ncp_rp_word_hl(rqp);    /* skip echo socket */
462         *ret_options = ncp_rp_byte(rqp);
463         NCP_RQ_EXIT;
464         return error;
465 }
466
467 static int
468 ncp_renegotiate_connparam(struct ncp_conn *conn, int buffsize, int in_options)
469 {
470         int neg_buffsize, error, options, sl;
471
472         sl = conn->li.sig_level;
473         if (sl >= 2)
474                 in_options |= NCP_SECURITY_LEVEL_SIGN_HEADERS;
475 #ifdef IPX
476         if (ipxcksum == 2)
477                 in_options |= NCP_IPX_CHECKSUM;
478 #endif
479         error = ncp_negotiate_size_and_options(conn, buffsize, in_options, 
480             &neg_buffsize, &options);
481         if (!error) {
482 #ifdef IPX
483                 if ((options ^ in_options) & NCP_IPX_CHECKSUM) {
484                         if (ipxcksum == 2) {
485                                 kprintf("Server refuses to support IPX checksums\n");
486                                 return NWE_REQUESTER_FAILURE;
487                         }
488                         in_options |= NCP_IPX_CHECKSUM;
489                         error = 1;
490                 }
491 #endif /* IPX */
492                 if ((options ^ in_options) & 2) {
493                         if (sl == 0 || sl == 3)
494                                 return NWE_SIGNATURE_LEVEL_CONFLICT;
495                         if (sl == 1) {
496                                 in_options |= NCP_SECURITY_LEVEL_SIGN_HEADERS;
497                                 error = 1;
498                         }
499                 }
500                 if (error) {
501                         error = ncp_negotiate_size_and_options(conn,
502                             buffsize, in_options, &neg_buffsize, &options);
503                         if ((options ^ in_options) & 3) {
504                                 return NWE_SIGNATURE_LEVEL_CONFLICT;
505                         }
506                 }
507         } else {
508                 in_options &= ~NCP_SECURITY_LEVEL_SIGN_HEADERS;
509                 error = ncp_negotiate_buffersize(conn, NCP_DEFAULT_BUFSIZE,
510                               &neg_buffsize);
511         }                         
512         if (error) return error;
513         if ((neg_buffsize < 512) || (neg_buffsize > NCP_MAX_BUFSIZE))
514                 return EINVAL;
515         conn->buffer_size = neg_buffsize;
516         if (in_options & NCP_SECURITY_LEVEL_SIGN_HEADERS)
517                 conn->flags |= NCPFL_SIGNWANTED;
518 #ifdef IPX
519         ncp_sock_checksum(conn, in_options & NCP_IPX_CHECKSUM);
520 #endif
521         return 0;
522 }
523
524 int
525 ncp_reconnect(struct ncp_conn *conn) {
526         int error;
527
528         /* close any open sockets */
529         ncp_sock_disconnect(conn);
530         switch( conn->li.addr.addr.sa_family ) {
531 #ifdef IPX
532             case AF_IPX:
533                 error = ncp_sock_connect_ipx(conn);
534                 break;
535 #endif
536 #ifdef INET
537             case AF_INET:
538                 error = ncp_sock_connect_in(conn);
539                 break;
540 #endif
541             default:
542                 return EPROTONOSUPPORT;
543         }
544         if (!error)
545                 error = ncp_ncp_connect(conn);
546         return error;
547 }
548
549 /*
550  * Create conn structure and try to do low level connect
551  * Server addr should be filled in.
552  */
553 int
554 ncp_connect(struct ncp_conn_args *li, struct thread *td, struct ucred *cred,
555         struct ncp_conn **aconn)
556 {
557         struct ncp_conn *conn;
558         struct ucred *owner;
559         int error, isroot;
560
561         if (li->addr.addr.sa_family != AF_INET && li->addr.addr.sa_family != AF_IPX)
562                 return EPROTONOSUPPORT;
563         isroot = ncp_suser(cred) == 0;
564         /*
565          * Only root can change ownership
566          */
567         if (li->owner != NCP_DEFAULT_OWNER && !isroot)
568                 return EPERM;
569         if (li->group != NCP_DEFAULT_GROUP &&
570             !groupmember(li->group, cred) && !isroot)
571                 return EPERM;
572         if (li->owner != NCP_DEFAULT_OWNER) {
573                 owner = crget();
574                 owner->cr_uid = li->owner;
575         } else {
576                 owner = crhold(cred);
577         }
578         error = ncp_conn_alloc(td, owner, &conn);
579         if (error)
580                 return (error);
581         if (error) {
582                 ncp_conn_free(conn);
583                 return error;
584         }
585         conn->li = *li;
586         conn->nc_group = (li->group != NCP_DEFAULT_GROUP) ? 
587                 li->group : cred->cr_groups[0];
588
589         if (li->retry_count == 0)
590                 conn->li.retry_count = NCP_RETRY_COUNT;
591         if (li->timeout == 0)
592                 conn->li.timeout = NCP_RETRY_TIMEOUT;
593         error = ncp_reconnect(conn);
594         if (error) {
595                 ncp_disconnect(conn);
596         } else {
597                 *aconn=conn;
598         }
599         return error;
600 }
601 /*
602  * Break connection and deallocate memory
603  */
604 int
605 ncp_disconnect(struct ncp_conn *conn) {
606
607         if (ncp_conn_access(conn,conn->ucred,NCPM_WRITE))
608                 return EACCES;
609         if (conn->ref_cnt != 0) return EBUSY;
610         if (conn->flags & NCPFL_PERMANENT) return EBUSY;
611         if (ncp_conn_valid(conn)) {
612                 ncp_ncp_disconnect(conn);
613         }
614         ncp_sock_disconnect(conn);
615         ncp_conn_free(conn);
616         return 0;
617 }
618
619 void
620 ncp_check_rq(struct ncp_conn *conn){
621         return;
622         if (conn->flags & NCPFL_INTR) return;
623         /* first, check for signals */
624         if (ncp_chkintr(conn,conn->td)) {
625                 conn->flags |= NCPFL_INTR;
626         }
627         return;
628 }