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