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