1 /*
2  * Copyright (c) 2005, 2008, 2009, 2010, 2011, 2012, 2013
3  *      Inferno Nettverk A/S, Norway.  All rights reserved.
4  *      Inferno Nettverk A/S, Norway.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. The above copyright notice, this list of conditions and the following
10  *    disclaimer must appear in all copies of the software, derivative works
11  *    or modified versions, and any portions thereof, aswell as in all
12  *    supporting documentation.
13  * 2. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by
16  *      Inferno Nettverk A/S, Norway.
17  * 3. The name of the author may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  *
31  * Inferno Nettverk A/S requests users of this software to return to
32  *
33  *  Software Distribution Coordinator  or  sdc@inet.no
34  *  Inferno Nettverk A/S
35  *  Oslo Research Park
36  *  Gaustadall�en 21
37  *  NO-0349 Oslo
38  *  Norway
39  *
40  * any improvements or extensions that they make and grant Inferno Nettverk A/S
41  * the rights to redistribute these changes.
42  *
43  */
44 
45 #include "common.h"
46 
47 static const char rcsid[] =
48 "$Id: session.c,v 1.102.4.2 2014/09/01 07:44:01 karls Exp $";
49 
50 static int
51 session_isavailable(shmem_object_t *ss, const clientinfo_t *cinfo,
52                     const int lock, const int mapisopen,
53                     char *emsg, size_t emsglen);
54 /*
55  * Returns true if there is at least one session free for use in the
56  * "ss" object, assuming the client is "cinfo".
57  * Returns false otherwise.
58  *
59  * Might also remove expired keystate entries from ss->keystate.
60 
61  * If "mapisopen" is true, any keymap in shmem to be used for keystate
62  * is already open.  Upon function return, the open/close state of the
63  * keystate map should remain as it was.
64  */
65 
66 static int
67 throttlepermits(const sessionthrottle_t *throttle,
68                 const struct timeval *starttime,
69                 const size_t client_since_starttime,
70                 const struct timeval *timenow);
71 /*
72  * Checks whether the throttle "throttle" permits one more client
73  * at time "timenow".  "clientsnow" is the current number of clients
74  * related to the "throttle".
75  *
76  * Returns true if the throttle permits, false if not.
77  */
78 
79 static char *
80 sessionlimitstring(const size_t inuse, const size_t max,
81                    char *buf, const size_t buflen);
82 /*
83  * Return error string corresponding to throttle limit having been reached.
84  */
85 
86 static char *
87 throttlelimitstring(const struct timeval *starttime,
88                     const struct timeval *timenow,
89                     const size_t newclients, const size_t currentclients,
90                     char *buf, const size_t buflen);
91 
92 /*
93  * Return error string corresponding to ratelimit having been reached.
94  */
95 
96 
97 
98 int
session_use(shmem,cinfo,lock,emsg,emsglen)99 session_use(shmem, cinfo, lock, emsg, emsglen)
100    shmem_object_t *shmem;
101    const clientinfo_t *cinfo;
102    const int lock;
103    char *emsg;
104    const size_t emsglen;
105 {
106    const char *function = "session_use()";
107 #if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
108    shmem_object_t _shmem = *shmem;
109 #endif /* DEBUG */
110    session_t *ss = &shmem->object.ss;
111    struct timeval timenow = { 0, 0 };
112    ssize_t index = -1;
113    size_t mappedsize;
114 
115    SASSERTX(shmem != NULL);
116 
117    slog(LOG_DEBUG, "%s: shmid %lu, cinfo = %s, lock = %d",
118         function, shmem->mstate.shmid, clientinfo2string(cinfo, NULL, 0), lock);
119 
120 #if HAVE_SOCKS_HOSTID
121    if (shmem->keystate.key == key_hostid)
122       if (shmem->keystate.keyinfo.hostindex > cinfo->hostidc) {
123          snprintf(emsg, emsglen,
124                   "session.state.hostindex specifies using hostindex #%u, but "
125                   "client connection provides %u hostindex%s.  "
126                   "Network/%s misconfiguration?",
127                   (unsigned)shmem->keystate.keyinfo.hostindex,
128                   (unsigned)cinfo->hostidc,
129                   (unsigned)cinfo->hostidc == 1 ? "" : "es",
130                   PRODUCT);
131 
132          slog(LOG_WARNING, "%s: problem related to %s #%lu detected: %s",
133               function,
134               objecttype2string(shmem->mstate.parenttype),
135               (unsigned long)shmem->mstate.number,
136               emsg);
137 
138          return 0;
139       }
140 #endif /* HAVE_SOCKS_HOSTID */
141 
142    socks_lock(lock, (off_t)shmem->mstate.shmid, 1, 1, 1);
143 
144    if (shmem->keystate.key == key_unset)
145       mappedsize = 0;
146    else {
147       int rc;
148 
149       MUNPROTECT_SHMEMHEADER(shmem);
150       rc = keystate_openmap(shmem->mstate.shmid, &shmem->keystate, &mappedsize);
151       MPROTECT_SHMEMHEADER(shmem);
152 
153       if (rc == -1) {
154          socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
155 
156          slog(LOG_DEBUG,
157               "%s: keystate_openmap() of shmid %ld/key %d, failed: %s",
158               function,
159               shmem->mstate.shmid,
160               (int)shmem->keystate.key,
161               strerror(errno));
162 
163          return 0;
164       }
165       else
166          slog(LOG_DEBUG,
167               "%s: opened mmap(2) of size %lu at address %p for shmid %lu",
168               function,
169               (unsigned long)mappedsize,
170               shmem->keystate.keyv,
171               (unsigned long)shmem->mstate.shmid);
172    }
173 
174    if (mappedsize != 0)
175       SASSERTX(shmem->keystate.keyv != NULL);
176 
177    if (!session_isavailable(shmem, cinfo, -1, 1, emsg, emsglen)) {
178       SASSERTX(mappedsize ==   shmem->keystate.keyc
179                              * sizeof(*shmem->keystate.keyv));
180 
181       MUNPROTECT_SHMEMHEADER(shmem);
182 
183       keystate_closemap(shmem->mstate.shmid, &shmem->keystate, mappedsize, -1);
184 
185       MPROTECT_SHMEMHEADER(shmem);
186 
187       /*
188        * *must* be after keystate_closemap() since keystate_closemap also
189        * NULL's the map and this will affect other processes who open it.
190        */
191       socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
192 
193       slog(LOG_DEBUG, "%s: no free session slots available: %s",
194            function, emsglen == 0 ? "" : emsg);
195 
196       return 0;
197    }
198 
199    if (shmem_use(shmem, cinfo, -1, 1) == -1) {
200       MUNPROTECT_SHMEMHEADER(shmem);
201 
202       keystate_closemap(shmem->mstate.shmid,
203                         &shmem->keystate,
204                         mappedsize,
205                         -1);
206 
207       MUNPROTECT_SHMEMHEADER(shmem);
208 
209       socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
210       return 0;
211    }
212 
213    /* may have changed if shmem_use() had to expand the array. */
214    mappedsize = shmem->keystate.keyc * sizeof(*shmem->keystate.keyv);
215 
216    if (ss->throttle_isset) {
217       struct timeval tdiff;
218 
219       if (!timerisset(&timenow))
220          gettimeofday_monotonic(&timenow);
221 
222       timersub(&timenow, &ss->throttle.starttime, &tdiff);
223       if (tdiff.tv_sec >= ss->throttle.limit.seconds)
224          RESET_THROTTLE(&ss->throttle, timenow);
225 
226       ++ss->throttle.newclients;
227    }
228 
229    if (ss->throttle_perstate_isset || ss->max_perstate_isset) {
230       /*
231        * shmem_use() should make sure an entry is allocated for this
232        * address by now.
233        */
234 
235       SASSERTX(mappedsize != 0);
236 
237       index = keystate_index(shmem, cinfo, 0);
238       SASSERTX(index >= 0);
239       SASSERTX((size_t)index < shmem->keystate.keyc);
240    }
241 
242    if (ss->throttle_perstate_isset) {
243       if (!timerisset(&timenow))
244          gettimeofday_monotonic(&timenow);
245 
246       if (keystate_hasexpired(shmem, (size_t)index, &timenow))
247          RESET_THROTTLE(&shmem->keystate.keyv[index].info.ss, timenow);
248 
249       ++shmem->keystate.keyv[index].info.ss.newclients;
250    }
251 
252    MUNPROTECT_SHMEMHEADER(shmem);
253 
254    keystate_closemap(shmem->mstate.shmid, &shmem->keystate, mappedsize, index);
255 
256    MUNPROTECT_SHMEMHEADER(shmem);
257 
258    socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
259    return 1;
260 }
261 
262 void
session_unuse(ss,cinfo,lock)263 session_unuse(ss, cinfo, lock)
264    shmem_object_t *ss;
265    const clientinfo_t *cinfo;
266    const int lock;
267 {
268    const char *function = "session_unuse()";
269 #if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
270    shmem_object_t _shmem = *ss;
271 #endif /* DEBUG */
272 
273    SASSERTX(ss != NULL);
274 
275    slog(LOG_DEBUG, "%s: shmid = %ld, key = %d, cinfo = %s, lock = %d",
276                    function,
277                    ss->mstate.shmid,
278                    (int)ss->keystate.key,
279                    clientinfo2string(cinfo, NULL, 0),
280                    lock);
281 
282    socks_lock(lock, (off_t)ss->mstate.shmid, 1, 1, 1);
283 
284    shmem_unuse(ss, cinfo, -1);
285 
286    if (sockscf.option.debug)
287       slog(LOG_DEBUG, "%s: sessions left after unuse on shmid %lu, key %d: %s",
288                       function,
289                       (unsigned long)ss->mstate.shmid,
290                       (int)ss->keystate.key,
291                       session_isavailable(ss, cinfo, -1, 0, NULL, 0) ?
292                            "yes" : "no");
293 
294    socks_unlock(lock, (off_t)ss->mstate.shmid, 1);
295 }
296 
297 static int
session_isavailable(shmem,cinfo,lock,mapisopen,emsg,emsglen)298 session_isavailable(shmem, cinfo, lock, mapisopen, emsg, emsglen)
299    shmem_object_t *shmem;
300    const clientinfo_t *cinfo;
301    const int lock;
302    const int mapisopen;
303    char *emsg;
304    size_t emsglen;
305 {
306    const char *function   = "session_isavailable()";
307 #if DEBUG /* memory-mapped file contents may not be saved in coredumps. */
308    shmem_object_t _shmem = *shmem;
309 #endif /* DEBUG */
310    const session_t *ss    = &shmem->object.ss;
311    const int doclosemap   = (mapisopen ? 0 : 1);
312    struct timeval timenow = { 0, 0 };
313    size_t mappedsize, inuse;
314    ssize_t i;
315    char buf[256], backupemsg[256];
316 
317    if (emsg == NULL || emsglen == 0) {
318       emsg    = backupemsg;
319       emsglen = sizeof(backupemsg);
320    }
321 
322    SASSERTX(ss->max_isset
323    ||       ss->throttle_isset
324    ||       shmem->keystate.key != key_unset);
325 
326    socks_lock(lock, (off_t)shmem->mstate.shmid, 1, 1, 1);
327 
328    /*
329     * Check not-keystate limits first.
330     */
331 
332    if (ss->throttle_isset) {
333       if (!timerisset(&timenow))
334          gettimeofday_monotonic(&timenow);
335 
336       if (!throttlepermits(&ss->throttle.limit,
337                            &ss->throttle.starttime,
338                            ss->throttle.newclients,
339                            &timenow)) {
340          throttlelimitstring(&ss->throttle.starttime,
341                              &timenow,
342                              ss->throttle.newclients,
343                              shmem->mstate.clients,
344                              emsg,
345                              emsglen);
346 
347          socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
348 
349          slog(LOG_DEBUG, "%s: throttlelimit reached for shmid %lu: %s",
350               function, (unsigned long)shmem->mstate.shmid, emsg);
351 
352          return 0;
353       }
354    }
355 
356    if (ss->max_isset) {
357       const size_t left = (unsigned long)(ss->max - shmem->mstate.clients);
358 
359       SASSERTX(shmem->mstate.clients <= ss->max);
360 
361       slog(LOG_DEBUG, "%s: sessions in use for shmid %lu: %lu, max: %lu",
362            function,
363            (unsigned long)shmem->mstate.shmid,
364            (unsigned long)shmem->mstate.clients,
365            (unsigned long)ss->max);
366 
367       if (left <= 0) {
368          sessionlimitstring(shmem->mstate.clients, ss->max, emsg, emsglen);
369 
370          socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
371          slog(LOG_DEBUG, "%s: sessionlimit reached for shmid %lu: %s",
372               function, (unsigned long)shmem->mstate.shmid, emsg);
373 
374          return 0;
375       }
376    }
377 
378    /*
379     * not-keystate limits are ok.  Now check keystate-based limits, if any.
380     */
381 
382    if (shmem->keystate.key == key_unset
383    || shmem->keystate.keyc == 0 /* limit can't have been reached. */) {
384       socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
385       return 1;
386    }
387 
388    if (mapisopen)
389       SASSERTX(shmem->keystate.keyv != NULL);
390    else {
391       int rc;
392 
393       MUNPROTECT_SHMEMHEADER(shmem);
394 
395       rc = keystate_openmap(shmem->mstate.shmid, &shmem->keystate, &mappedsize);
396 
397       MPROTECT_SHMEMHEADER(shmem);
398 
399       if (rc == -1) {
400          socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
401 
402          slog(LOG_DEBUG,
403               "%s: keystate_openmap() of shmid %lu/key %d failed: %s",
404               function,
405               shmem->mstate.shmid,
406               (int)shmem->keystate.key,
407               strerror(errno));
408 
409          return 0;
410       }
411    }
412 
413    SASSERTX(shmem->keystate.keyv != NULL);
414 
415    if ((i = keystate_index(shmem, cinfo, 0)) != -1) {
416       SASSERTX(i >= 0);
417       SASSERTX((size_t)i < shmem->keystate.keyc);
418 
419       switch (shmem->keystate.key) {
420          case key_from:
421             inuse = shmem->keystate.keyv[i].data.from.addrc;
422             break;
423 
424 #if HAVE_SOCKS_HOSTID
425          case key_hostid:
426             inuse = shmem->keystate.keyv[i].data.hostid.addrc;
427             break;
428 #endif /* HAVE_SOCKS_HOSTID */
429 
430          default:
431             SERRX(shmem->keystate.key);
432       }
433 
434       if (ss->max_perstate_isset) {
435          slog(LOG_DEBUG,
436               "%s: per-key %s sessions in use for shmid %lu: %ld, max: %lu",
437               function,
438               statekey2string(shmem->keystate.key),
439               (unsigned long)shmem->mstate.shmid,
440               (long)inuse,
441               (unsigned long)ss->max_perstate);
442 
443          SASSERTX(inuse <= ss->max_perstate);
444 
445          if (ss->max_perstate - inuse <= 0) {
446             snprintf(emsg, emsglen, "per-key %s",
447                      sessionlimitstring((size_t)inuse,
448                                         ss->max_perstate,
449                                         buf,
450                                         sizeof(buf)));
451             slog(LOG_DEBUG,
452                  "%s: per-key sessionlimit reached for shmid %lu: %s",
453                  function, (unsigned long)shmem->mstate.shmid, emsg);
454 
455             if (doclosemap) {
456                MUNPROTECT_SHMEMHEADER(shmem);
457 
458                keystate_closemap(shmem->mstate.shmid,
459                                  &shmem->keystate,
460                                  mappedsize,
461                                  -1);
462 
463                MUNPROTECT_SHMEMHEADER(shmem);
464             }
465 
466             socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
467             return 0;
468          }
469       }
470 
471       if (ss->throttle_perstate_isset) {
472          SASSERTX(ss->throttle_perstate.limit.clients != 0);
473          SASSERTX(ss->throttle_perstate.limit.seconds != 0);
474 
475          if (!timerisset(&timenow))
476             gettimeofday_monotonic(&timenow);
477 
478          if (!throttlepermits(&ss->throttle_perstate.limit,
479                               &shmem->keystate.keyv[i].info.ss.starttime,
480                               shmem->keystate.keyv[i].info.ss.newclients,
481                               &timenow)) {
482             snprintf(emsg, emsglen, "per-key %s",
483                      throttlelimitstring(
484                                      &shmem->keystate.keyv[i].info.ss.starttime,
485                                      &timenow,
486                                      shmem->keystate.keyv[i].info.ss.newclients,
487                                      inuse,
488                                      buf,
489                                      sizeof(buf)));
490 
491             slog(LOG_DEBUG,
492                  "%s: per-key throttlelimit reached for shmid %lu: %s",
493                  function, (unsigned long)shmem->mstate.shmid, emsg);
494 
495             if (doclosemap) {
496                MUNPROTECT_SHMEMHEADER(shmem);
497 
498                keystate_closemap(shmem->mstate.shmid,
499                                  &shmem->keystate,
500                                  mappedsize,
501                                  -1);
502 
503                MPROTECT_SHMEMHEADER(shmem);
504             }
505 
506             socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
507             return 0;
508          }
509       }
510    }
511 
512    if (doclosemap) {
513       MUNPROTECT_SHMEMHEADER(shmem);
514 
515       keystate_closemap(shmem->mstate.shmid, &shmem->keystate, mappedsize, -1);
516 
517       MPROTECT_SHMEMHEADER(shmem);
518    }
519 
520    socks_unlock(lock, (off_t)shmem->mstate.shmid, 1);
521    return 1;
522 }
523 
524 static int
throttlepermits(throttle,starttime,newclients,timenow)525 throttlepermits(throttle, starttime, newclients, timenow)
526    const sessionthrottle_t *throttle;
527    const struct timeval *starttime;
528    const size_t newclients;
529    const struct timeval *timenow;
530 {
531    const char *function = "throttlepermits()";
532    struct timeval tdiff;
533 
534    slog(LOG_DEBUG,
535         "%s: throttle: %lu/%ld, starttime: %ld.%06ld, newclients: %lu, "
536         "timenow: %ld.%06ld",
537         function,
538         (unsigned long)throttle->clients,
539         (long)throttle->seconds,
540         (long)starttime->tv_sec,
541         (long)starttime->tv_usec,
542         (unsigned long)newclients,
543         (long)timenow->tv_sec,
544         (long)timenow->tv_usec);
545 
546    if (newclients < throttle->clients)
547       return 1;
548 
549    timersub(timenow, starttime, &tdiff);
550 
551    if (tdiff.tv_sec >= throttle->seconds)
552       return 1;
553 
554    return 0;
555 }
556 
557 static char *
sessionlimitstring(inuse,max,buf,buflen)558 sessionlimitstring(inuse, max, buf, buflen)
559    const size_t inuse;
560    const size_t max;
561    char *buf;
562    size_t buflen;
563 {
564 
565    SASSERTX(inuse <= max);
566 
567    snprintf(buf, buflen, "session limit reached (%ld/%ld slot%s in use)",
568             (long)inuse, (long)max, max == 1 ? "" : "s");
569 
570    return buf;
571 }
572 
573 static char *
throttlelimitstring(starttime,timenow,newclients,currentclients,buf,buflen)574 throttlelimitstring(starttime, timenow, newclients, currentclients, buf, buflen)
575    const struct timeval *starttime;
576    const struct timeval *timenow;
577    const size_t newclients;
578    const size_t currentclients;
579    char *buf;
580    const size_t buflen;
581 {
582    struct timeval tdiff;
583 
584    timersub(timenow, starttime, &tdiff);
585    snprintf(buf, buflen,
586             "session rate limit reached (already accepted %lu new client%s "
587             "during the last %lds.  Current client count: %lu)",
588             (unsigned long)newclients,
589             (unsigned long)newclients == 1 ? "" : "s",
590             (long)timeval2seconds(&tdiff),
591             (unsigned long)currentclients);
592 
593    return buf;
594 }
595