d96473e4fcd6972d07085a7500bf06e70197beb9
[dragonfly.git] / sys / netproto / ncp / ncp_conn.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  * $FreeBSD: src/sys/netncp/ncp_conn.c,v 1.3.2.5 2001/02/22 08:54:11 bp Exp $
32  * $DragonFly: src/sys/netproto/ncp/ncp_conn.c,v 1.15 2007/05/13 18:33:58 swildner Exp $
33  *
34  * Connection tables
35  */
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/kernel.h>
39 #include <sys/malloc.h>
40 #include <sys/proc.h>
41 #include <sys/priv.h>
42 #include <sys/lock.h>
43 #include <sys/sysctl.h>
44
45 #include "ncp.h"
46 #include "ncp_subr.h"
47 #include "ncp_conn.h"
48
49 SLIST_HEAD(ncp_handle_head,ncp_handle);
50
51 int ncp_burst_enabled = 1;
52
53 struct ncp_conn_head conn_list={NULL};
54 static int ncp_conn_cnt = 0;
55 static int ncp_next_ref = 1;
56 static struct lock listlock;
57
58 struct ncp_handle_head lhlist={NULL};
59 static int ncp_next_handle = 1;
60 static struct lock lhlock;
61
62 static int ncp_sysctl_connstat(SYSCTL_HANDLER_ARGS);
63 static int ncp_conn_lock_any(struct ncp_conn *conn, struct thread *td, 
64     struct ucred *cred);
65
66 SYSCTL_DECL(_net_ncp);
67 SYSCTL_INT (_net_ncp, OID_AUTO, burst_enabled, CTLFLAG_RD, &ncp_burst_enabled, 0, "");
68 SYSCTL_INT (_net_ncp, OID_AUTO, conn_cnt, CTLFLAG_RD, &ncp_conn_cnt, 0, "");
69 SYSCTL_PROC(_net_ncp, OID_AUTO, conn_stat, CTLFLAG_RD|CTLTYPE_OPAQUE,
70             NULL, 0, ncp_sysctl_connstat, "S,connstat", "Connections list");
71
72 MALLOC_DEFINE(M_NCPDATA, "NCP data", "NCP private data");
73
74 int
75 ncp_conn_init(void) {
76         lockinit(&listlock, "ncpll", 0, 0);
77         lockinit(&lhlock, "ncplh", 0, 0);
78         return 0;
79 }
80
81 int
82 ncp_conn_locklist(int flags, struct thread *td)
83 {
84         return lockmgr(&listlock, flags | LK_CANRECURSE);
85 }
86
87 void
88 ncp_conn_unlocklist(struct thread *td)
89 {
90         lockmgr(&listlock, LK_RELEASE);
91 }
92
93 int
94 ncp_conn_access(struct ncp_conn *conn, struct ucred *cred, mode_t mode)
95 {
96         int error;
97
98         if (cred == NOCRED || ncp_suser(cred) == 0 ||
99             cred->cr_uid == conn->nc_owner->cr_uid)
100                 return 0;
101         mode >>= 3;
102         if (!groupmember(conn->nc_group, cred))
103                 mode >>= 3;
104         error = (conn->li.access_mode & mode) == mode ? 0 : EACCES;
105         return error;
106 }
107
108 /*
109  * NOTE: The lockmgr() call is protected by nc_lwant
110  */
111 int
112 ncp_conn_lock_any(struct ncp_conn *conn, struct thread *td, struct ucred *cred)
113 {
114         int error;
115
116         if (conn->nc_id == 0) 
117                 return EACCES;
118         error = lockmgr(&conn->nc_lock, LK_EXCLUSIVE | LK_CANRECURSE);
119         if (error == ERESTART)
120                 return EINTR;
121         error = ncp_chkintr(conn, td);
122         if (error) {
123                 lockmgr(&conn->nc_lock, LK_RELEASE);
124                 return error;
125         }
126
127         if (conn->nc_id == 0) {
128                 lockmgr(&conn->nc_lock, LK_RELEASE);
129                 return EACCES;
130         }
131         conn->td = td;  /* who currently operates */
132         conn->ucred = cred;
133         return 0;
134 }
135
136 /*
137  * NOTE: The lockmgr() call should be protected by nc_lwant, but it
138  * is unclear whether callers have coded this correctly.
139  */
140 int
141 ncp_conn_lock(struct ncp_conn *conn, struct thread *td,
142         struct ucred *cred, int mode)
143 {
144         int error;
145
146         error = ncp_conn_access(conn, cred, mode);
147         if (error)
148                 return error;
149         return ncp_conn_lock_any(conn, td, cred);
150 }
151
152 /*
153  * Lock conn but unlock connlist
154  */
155 static int
156 ncp_conn_lock2(struct ncp_conn *conn, struct thread *td,
157         struct ucred *cred, int mode) 
158 {
159         int error;
160
161         error = ncp_conn_access(conn, cred, mode);
162         if (error) {
163                 ncp_conn_unlocklist(td);
164                 return error;
165         }
166         conn->nc_lwant++;
167         ncp_conn_unlocklist(td);
168         error = ncp_conn_lock_any(conn, td, cred);
169         conn->nc_lwant--;
170         if (conn->nc_lwant == 0) {
171                 wakeup(&conn->nc_lwant);
172         }
173         return error;
174 }
175
176 void
177 ncp_conn_unlock(struct ncp_conn *conn, struct thread *td) {
178         /*
179          * note, that LK_RELASE will do wakeup() instead of wakeup_one().
180          * this will do a little overhead
181          */
182         lockmgr(&conn->nc_lock, LK_RELEASE);
183 }
184
185 int 
186 ncp_conn_assert_locked(struct ncp_conn *conn, const char *checker,
187                        struct thread *td)
188 {
189         if (conn->nc_lock.lk_flags & LK_HAVE_EXCL)
190                 return 0;
191         kprintf("%s: connection isn't locked!\n", checker);
192         return EIO;
193 }
194
195 /* 
196  * create, fill with defaults and return in locked state
197  */
198 int
199 ncp_conn_alloc(struct thread *td, struct ucred *cred, struct ncp_conn **conn)
200 {
201         int error;
202         struct ncp_conn *ncp;
203
204         MALLOC(ncp, struct ncp_conn *, sizeof(struct ncp_conn), 
205             M_NCPDATA, M_WAITOK | M_ZERO);
206         error = 0;
207         lockinit(&ncp->nc_lock, "ncplck", 0, 0);
208         ncp_conn_cnt++;
209         ncp->nc_id = ncp_next_ref++;
210         ncp->nc_owner = cred;
211         ncp->seq = 0;
212         ncp->connid = 0xFFFF;
213         ncp_conn_lock_any(ncp, td, ncp->nc_owner);
214         *conn = ncp;
215         ncp_conn_locklist(LK_EXCLUSIVE, td);
216         SLIST_INSERT_HEAD(&conn_list,ncp,nc_next);
217         ncp_conn_unlocklist(td);
218         return (error);
219 }
220
221 /*
222  * Remove the connection, on entry it must be locked
223  */
224 int
225 ncp_conn_free(struct ncp_conn *ncp)
226 {
227         int error;
228         struct ncp_conn *ncp1;
229
230         if (ncp == NULL) {
231                 NCPFATAL("conn==NULL !\n");
232                 return(EIO);
233         }
234         if (ncp->nc_id == 0) {
235                 kprintf("already!!!!\n");
236                 return EACCES;
237         }
238         error = ncp_conn_assert_locked(ncp, __func__, ncp->td);
239         if (error)
240                 return error;
241         if (ncp->ref_cnt) {
242                 NCPFATAL("there are %d references left\n",ncp->ref_cnt);
243                 return(EBUSY);
244         }
245
246         /*
247          * Mark conn as died and wait for other process.  This also
248          * interlocks against other ncp_conn_free() ops.
249          */
250         ncp->nc_id = 0;
251         while (ncp->nc_lwant) {
252                 ncp_conn_unlock(ncp, ncp->td);
253                 kprintf("lwant = %d\n", ncp->nc_lwant);
254                 tsleep(&ncp->nc_lwant, 0, "ncpdr", 2*hz);
255                 lockmgr(&ncp->nc_lock, LK_EXCLUSIVE);
256         }
257         ncp_conn_locklist(LK_EXCLUSIVE, ncp->td);
258         SLIST_FOREACH(ncp1, &conn_list, nc_next) {
259                 if (ncp1 == ncp)
260                         break;
261         }
262         KKASSERT(ncp1 == ncp);
263         SLIST_REMOVE(&conn_list, ncp, ncp_conn, nc_next);
264         ncp_conn_cnt--;
265         ncp_conn_unlocklist(ncp->td);
266         if (ncp->li.user) kfree(ncp->li.user, M_NCPDATA);
267         if (ncp->li.password) kfree(ncp->li.password, M_NCPDATA);
268         crfree(ncp->nc_owner);
269         FREE(ncp, M_NCPDATA);
270         return (0);
271 }
272
273 /* 
274  * Lookup connection by handle, return a locked conn descriptor 
275  */
276 int
277 ncp_conn_getbyref(int ref,struct thread *td,struct ucred *cred, int mode, struct ncp_conn **connpp){
278         struct ncp_conn *ncp;
279         int error=0;
280
281         ncp_conn_locklist(LK_SHARED, td);
282         SLIST_FOREACH(ncp, &conn_list, nc_next)
283                 if (ncp->nc_id == ref) break;
284         if (ncp == NULL) {
285                 ncp_conn_unlocklist(td);
286                 return(EBADF);
287         }
288         error = ncp_conn_lock2(ncp, td, cred, mode);
289         if (!error)
290                 *connpp = ncp;
291         return (error);
292 }
293 /*
294  * find attached, but not logged in connection to specified server
295  */
296 int
297 ncp_conn_getattached(struct ncp_conn_args *li,struct thread *td,struct ucred *cred,int mode, struct ncp_conn **connpp){
298         struct ncp_conn *ncp, *ncp2=NULL;
299         int error = 0;
300
301         ncp_conn_locklist(LK_SHARED, td);
302         SLIST_FOREACH(ncp, &conn_list, nc_next) {
303                 if ((ncp->flags & NCPFL_LOGGED) != 0 ||
304                     strcmp(ncp->li.server,li->server) != 0 || 
305                     ncp->li.saddr.sa_len != li->saddr.sa_len ||
306                     bcmp(&ncp->li.saddr,&ncp->li.saddr,li->saddr.sa_len) != 0)
307                         continue;
308                 if (ncp_suser(cred) == 0 || 
309                     cred->cr_uid == ncp->nc_owner->cr_uid)
310                         break;
311                 error = ncp_conn_access(ncp,cred,mode);
312                 if (!error && ncp2 == NULL)
313                         ncp2 = ncp;
314         }
315         if (ncp == NULL) ncp = ncp2;
316         if (ncp == NULL) {
317                 ncp_conn_unlocklist(td);
318                 return(EBADF);
319         }
320         error = ncp_conn_lock2(ncp,td,cred,mode);
321         if (!error)
322                 *connpp=ncp;
323         return (error);
324 }
325
326 /* 
327  * Lookup connection by server/user pair, return a locked conn descriptor.
328  * if li is NULL or server/user pair incomplete, try to select best connection 
329  * based on owner.
330  * Connection selected in next order:
331  * 1. Try to search conn with ucred owner, if li is NULL also find a primary
332  * 2. If 1. fails try to get first suitable shared connection
333  * 3. If 2. fails then nothing can help to poor ucred owner
334  */
335
336 int
337 ncp_conn_getbyli(struct ncp_conn_args *li, struct thread *td,
338         struct ucred *cred, int mode, struct ncp_conn **connpp)
339 {
340         struct ncp_conn *ncp, *ncp2=NULL;
341         int error=0, partial, haveserv;
342
343         partial = (li == NULL || li->server[0] == 0 || li->user == NULL);
344         haveserv = (li && li->server[0]);
345         ncp_conn_locklist(LK_SHARED, td);
346         SLIST_FOREACH(ncp, &conn_list, nc_next) {
347                 if (partial) {
348                         if (cred->cr_uid == ncp->nc_owner->cr_uid) {
349                                 if (haveserv) {
350                                         if (strcmp(ncp->li.server,li->server) == 0)
351                                                 break;
352                                 } else {
353                                         if (ncp->flags & NCPFL_PRIMARY)
354                                                 break;
355                                         ncp2 = ncp;
356                                 }
357                                 continue;
358                         }
359                 } else {
360                         if (strcmp(ncp->li.server,li->server) != 0 || 
361                             ncp->li.user == NULL ||
362                             strcmp(ncp->li.user,li->user) != 0)
363                                 continue;
364                         if (cred->cr_uid == ncp->nc_owner->cr_uid)
365                                 break;
366                         if (ncp_suser(cred) == 0)
367                                 ncp2 = ncp;
368                 }
369                 error = ncp_conn_access(ncp,cred,mode);
370                 if (!error && ncp2 == NULL)
371                         ncp2 = ncp;
372         }
373         if (ncp == NULL) ncp = ncp2;
374         if (ncp == NULL) {
375                 ncp_conn_unlocklist(td);
376                 return(EBADF);
377         }
378         error = ncp_conn_lock2(ncp,td,cred,mode);
379         if (!error)
380                 *connpp=ncp;
381         return (error);
382 }
383
384 /*
385  * Set primary connection flag, since it have sence only for an owner,
386  * only owner can modify this flag.
387  * connection expected to be locked.
388  */
389 int
390 ncp_conn_setprimary(struct ncp_conn *conn, int on){
391         struct ncp_conn *ncp=NULL;
392
393         if (conn->ucred->cr_uid != conn->nc_owner->cr_uid)
394                 return EACCES;
395         ncp_conn_locklist(LK_SHARED, conn->td);
396         SLIST_FOREACH(ncp, &conn_list, nc_next) {
397                 if (conn->ucred->cr_uid == ncp->nc_owner->cr_uid)
398                         ncp->flags &= ~NCPFL_PRIMARY;
399         }
400         ncp_conn_unlocklist(conn->td);
401         if (on)
402                 conn->flags |= NCPFL_PRIMARY;
403         return 0;
404 }
405 /* 
406  * Lease conn to given proc, returning unique handle
407  * problem: how locks should be applied ?
408  */
409 int
410 ncp_conn_gethandle(struct ncp_conn *conn, struct thread *td, struct ncp_handle **handle){
411         struct ncp_handle *refp;
412
413         lockmgr(&lhlock, LK_EXCLUSIVE);
414         SLIST_FOREACH(refp, &lhlist, nh_next)
415                 if (refp->nh_conn == conn && td == refp->nh_td) break;
416         if (refp) {
417                 conn->ref_cnt++;
418                 refp->nh_ref++;
419                 *handle = refp;
420                 lockmgr(&lhlock, LK_RELEASE);
421                 return 0;
422         }
423         MALLOC(refp,struct ncp_handle *,sizeof(struct ncp_handle),M_NCPDATA,
424             M_WAITOK | M_ZERO);
425         SLIST_INSERT_HEAD(&lhlist,refp,nh_next);
426         refp->nh_ref++;
427         refp->nh_td = td;
428         refp->nh_conn = conn;
429         refp->nh_id = ncp_next_handle++;
430         *handle = refp;
431         conn->ref_cnt++;
432         lockmgr(&lhlock, LK_RELEASE);
433         return 0;
434 }
435 /*
436  * release reference, if force - ignore refcount
437  */
438 int
439 ncp_conn_puthandle(struct ncp_handle *handle, struct thread *td, int force) {
440         struct ncp_handle *refp = handle;
441
442         lockmgr(&lhlock, LK_EXCLUSIVE);
443         refp->nh_ref--;
444         refp->nh_conn->ref_cnt--;
445         if (force) {
446                 refp->nh_conn->ref_cnt -= refp->nh_ref;
447                 refp->nh_ref = 0;
448         }
449         if (refp->nh_ref == 0) {
450                 SLIST_REMOVE(&lhlist, refp, ncp_handle, nh_next);
451                 FREE(refp, M_NCPDATA);
452         }
453         lockmgr(&lhlock, LK_RELEASE);
454         return 0;
455 }
456 /*
457  * find a connHandle
458  */
459 int
460 ncp_conn_findhandle(int connHandle, struct thread *td, struct ncp_handle **handle) {
461         struct ncp_handle *refp;
462
463         lockmgr(&lhlock, LK_SHARED);
464         SLIST_FOREACH(refp, &lhlist, nh_next)
465                 if (refp->nh_td == td && refp->nh_id == connHandle) break;
466         lockmgr(&lhlock, LK_RELEASE);
467         if (refp == NULL) {
468                 return EBADF;
469         }
470         *handle = refp;
471         return 0;
472 }
473 /*
474  * Clear handles associated with specified process
475  */
476 int
477 ncp_conn_putprochandles(struct thread *td) {
478         struct ncp_handle *hp, *nhp;
479         int haveone = 0;
480
481         lockmgr(&lhlock, LK_EXCLUSIVE);
482         for (hp = SLIST_FIRST(&lhlist); hp; hp = nhp) {
483                 nhp = SLIST_NEXT(hp, nh_next);
484                 if (hp->nh_td != td) continue;
485                 haveone = 1;
486                 hp->nh_conn->ref_cnt -= hp->nh_ref;
487                 SLIST_REMOVE(&lhlist, hp, ncp_handle, nh_next);
488                 FREE(hp, M_NCPDATA);
489         }
490         lockmgr(&lhlock, LK_RELEASE);
491         return haveone;
492 }
493 /*
494  * remove references in all possible connections,
495  * XXX - possible problem is a locked list.
496  */
497 /*void
498 ncp_conn_list_rm_ref(pid_t pid) {
499         struct ncp_conn *ncp;
500
501         ncp_conn_locklist(LK_SHARED, NULL);
502         SLIST_FOREACH(ncp, &conn_list, nc_next) {
503                 ncp_conn_rm_ref(ncp,pid,1);
504         }
505         ncp_conn_unlocklist(NULL);
506         return;
507 }
508 */
509 int
510 ncp_conn_getinfo(struct ncp_conn *ncp, struct ncp_conn_stat *ncs) {
511         bzero(ncs,sizeof(*ncs));
512         ncs->li = ncp->li;
513         ncs->li.user = ncs->user;
514         if (ncp->li.user)
515                 strcpy(ncs->user, ncp->li.user);
516         ncs->li.password = NULL;
517         ncs->connRef = ncp->nc_id;
518         ncs->ref_cnt = ncp->ref_cnt;
519         ncs->connid = ncp->connid;
520         ncs->owner = ncp->nc_owner->cr_uid;
521         ncs->group = ncp->nc_group;
522         ncs->flags = ncp->flags;
523         ncs->buffer_size = ncp->buffer_size;
524         return 0;
525 }
526
527 static int
528 ncp_sysctl_connstat(SYSCTL_HANDLER_ARGS) {
529         int error;
530         struct ncp_conn_stat ncs;
531         struct ncp_conn *ncp;
532 /*      struct ucred *cred = req->p->p_ucred;*/
533
534         error = 0;
535         ncp_conn_locklist(LK_SHARED, req->td);
536         error = SYSCTL_OUT(req, &ncp_conn_cnt, sizeof(ncp_conn_cnt));
537         SLIST_FOREACH(ncp, &conn_list, nc_next) {
538                 if (error) break;
539                 /* I can't do conn_lock while list is locked */
540                 ncp->nc_lwant++;
541                 if (!error) {
542                         ncp_conn_getinfo(ncp, &ncs);
543                 } else {
544                         bzero(&ncs,sizeof(ncs));
545                         ncs.connRef = ncp->nc_id;
546                         strcpy(ncs.li.server,"***");
547                 }
548                 ncp->nc_lwant--;
549                 error = SYSCTL_OUT(req, &ncs, sizeof(ncs));
550         }
551         ncp_conn_unlocklist(req->td);
552         return(error);
553 }