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