Merge branch 'vendor/MDOCML'
[dragonfly.git] / sbin / iscontrol / fsm.c
1 /*-
2  * Copyright (c) 2005-2008 Daniel Braniss <danny@cs.huji.ac.il>
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  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  */
27
28 /*
29  | $Id: fsm.c,v 2.8 2007/05/19 16:34:21 danny Exp danny $
30  */
31
32 #include <sys/param.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/sysctl.h>
36
37 #include <netinet/in.h>
38 #include <netinet/tcp.h>
39 #include <arpa/inet.h>
40 #include <sys/ioctl.h>
41 #include <netdb.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <time.h>
49 #include <syslog.h>
50 #include <stdarg.h>
51 #include <camlib.h>
52
53 #include "iscsi.h"
54 #include "iscontrol.h"
55
56 typedef enum {
57      T1 = 1,
58      T2, /*T3,*/ T4, T5, /*T6,*/ T7, T8, T9,
59      T10, T11, T12, T13, T14, T15, T16, T18
60 } trans_t;
61
62 /*
63  | now supports IPV6
64  | thanks to:
65  |      Hajimu UMEMOTO @ Internet Mutual Aid Society Yokohama, Japan
66  |      ume@mahoroba.org  ume@{,jp.}FreeBSD.org
67  |      http://www.imasy.org/~ume/
68  */
69 static trans_t
70 tcpConnect(isess_t *sess)
71 {
72      isc_opt_t *op = sess->op;
73      int        val, sv_errno, soc;
74      struct     addrinfo *res, *res0, hints;
75      char       pbuf[10];
76
77      debug_called(3);
78      if(sess->flags & (SESS_RECONNECT|SESS_REDIRECT)) {
79           syslog(LOG_INFO, "%s", (sess->flags & SESS_RECONNECT)
80                  ? "Reconnect": "Redirected");
81
82           debug(1, "%s", (sess->flags & SESS_RECONNECT) ? "Reconnect": "Redirected");
83           shutdown(sess->soc, SHUT_RDWR);
84           //close(sess->soc);
85           sess->soc = -1;
86
87           sess->flags &= ~SESS_CONNECTED;
88           if(sess->flags & SESS_REDIRECT) {
89                sess->redirect_cnt++;
90                sess->flags |= SESS_RECONNECT;
91           } else
92                sleep(2); // XXX: actually should be ?
93 #ifdef notyet
94           {
95                time_t   sec;
96           // make sure we are not in a loop
97           // XXX: this code has to be tested
98           sec = time(0) - sess->reconnect_time;
99           if(sec > (5*60)) {
100                // if we've been connected for more that 5 minutes
101                // then just reconnect
102                sess->reconnect_time = sec;
103                sess->reconnect_cnt1 = 0;
104           }
105           else {
106                //
107                sess->reconnect_cnt1++;
108                if((sec / sess->reconnect_cnt1) < 2) {
109                     // if less that 2 seconds from the last reconnect
110                     // we are most probably looping
111                     syslog(LOG_CRIT, "too many reconnects %d", sess->reconnect_cnt1);
112                     return 0;
113                }
114           }
115      }
116 #endif
117           sess->reconnect_cnt++;
118      }
119
120      snprintf(pbuf, sizeof(pbuf), "%d", op->port);
121      memset(&hints, 0, sizeof(hints));
122      hints.ai_family    = PF_UNSPEC;
123      hints.ai_socktype  = SOCK_STREAM;
124      debug(1, "targetAddress=%s port=%d", op->targetAddress, op->port);
125      if((val = getaddrinfo(op->targetAddress, pbuf, &hints, &res0)) != 0) {
126           fprintf(stderr, "getaddrinfo(%s): %s\n", op->targetAddress, gai_strerror(val));
127           return 0;
128      }
129      sess->flags &= ~SESS_CONNECTED;
130      sv_errno = 0;
131      soc = -1;
132      for(res = res0; res; res = res->ai_next) {
133           soc = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
134           if (soc == -1)
135                continue;
136
137      // from Patrick.Guelat@imp.ch:
138      // iscontrol can be called without waiting for the socket entry to time out
139           val = 1;
140           if(setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &val, (socklen_t)sizeof(val)) < 0) {
141                   fprintf(stderr, "Cannot set socket SO_REUSEADDR %d: %s\n\n",
142                                   errno, strerror(errno));
143           }
144           if(connect(soc, res->ai_addr, res->ai_addrlen) == 0)
145              break;
146
147           sv_errno = errno;
148           close(soc);
149           soc = -1;
150      }
151      freeaddrinfo(res0);
152
153      if(soc != -1) {
154           sess->soc = soc;
155
156           /* Default to TCP_NODELAY to improve transfers */
157           if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
158                   fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n",
159                                   errno, strerror(errno));
160
161 #if 0
162           struct        timeval timeout;
163
164           val = 1;
165           if(setsockopt(sess->soc, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0)
166                fprintf(stderr, "Cannot set socket KEEPALIVE option err=%d %s\n",
167                        errno, strerror(errno));
168
169           if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
170                fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n",
171                        errno, strerror(errno));
172
173           timeout.tv_sec = 10;
174           timeout.tv_usec = 0;
175           if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0)
176              || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)) {
177                fprintf(stderr, "Cannot set socket timeout to %ld err=%d %s\n",
178                        timeout.tv_sec, errno, strerror(errno));
179           }
180 #endif
181 #ifdef CURIOUS
182           {
183                int len = sizeof(val);
184                if(getsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0)
185                     fprintf(stderr, "was: SO_SNDBUF=%dK\n", val/1024);
186           }
187 #endif
188           if(sess->op->sockbufsize) {
189                val = sess->op->sockbufsize * 1024;
190                if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0)
191                   || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0)) {
192                     fprintf(stderr, "Cannot set socket sndbuf & rcvbuf to %d err=%d %s\n",
193                             val, errno, strerror(errno));
194                     return 0;
195                }
196           }
197           sess->flags |= SESS_CONNECTED;
198           return T1;
199      }
200
201      fprintf(stderr, "errno=%d\n", sv_errno);
202      perror("connect");
203      switch(sv_errno) {
204      case ECONNREFUSED:
205      case ENETUNREACH:
206      case ETIMEDOUT:
207           if((sess->flags & SESS_REDIRECT) == 0) {
208                if(strcmp(op->targetAddress, sess->target.address) != 0) {
209                     syslog(LOG_INFO, "reconnecting to original target address");
210                     free(op->targetAddress);
211                     op->targetAddress           = sess->target.address;
212                     op->port                    = sess->target.port;
213                     op->targetPortalGroupTag    = sess->target.pgt;
214                     return T1;
215                }
216           }
217           sleep(5); // for now ...
218           return T1;
219      default:
220           return 0; // terminal error
221      }
222 }
223
224 int
225 setOptions(isess_t *sess, int flag)
226 {
227      isc_opt_t  oop;
228      char       *sep;
229
230      debug_called(3);
231
232      bzero(&oop, sizeof(isc_opt_t));
233
234      if((flag & SESS_FULLFEATURE) == 0) {
235           oop.initiatorName     = sess->op->initiatorName;
236           oop.targetAddress     = sess->op->targetAddress;
237           if(sess->op->targetName != 0)
238                oop.targetName = sess->op->targetName;
239
240           oop.maxRecvDataSegmentLength = sess->op->maxRecvDataSegmentLength;
241           oop.maxXmitDataSegmentLength = sess->op->maxXmitDataSegmentLength; // XXX:
242           oop.maxBurstLength = sess->op->maxBurstLength;
243           oop.maxluns = sess->op->maxluns;
244      }
245      else {
246           /*
247            | turn on digestion only after login
248            */
249           if(sess->op->headerDigest != NULL) {
250                sep = strchr(sess->op->headerDigest, ',');
251                if(sep == NULL)
252                     oop.headerDigest = sess->op->headerDigest;
253                debug(1, "oop.headerDigest=%s", oop.headerDigest);
254           }
255           if(sess->op->dataDigest != NULL) {
256                sep = strchr(sess->op->dataDigest, ',');
257                if(sep == NULL)
258                     oop.dataDigest = sess->op->dataDigest;
259                debug(1, "oop.dataDigest=%s", oop.dataDigest);
260           }
261      }
262
263      if(ioctl(sess->fd, ISCSISETOPT, &oop)) {
264           perror("ISCSISETOPT");
265           return -1;
266      }
267      return 0;
268 }
269
270 static trans_t
271 startSession(isess_t *sess)
272 {
273
274      int        n, fd, nfd;
275      char       *dev;
276
277      debug_called(3);
278
279      if((sess->flags & SESS_CONNECTED) == 0) {
280           return T2;
281      }
282      if(sess->fd == -1) {
283           fd = open(iscsidev, O_RDWR);
284           if(fd < 0) {
285                perror(iscsidev);
286                return 0;
287           }
288           {
289                // XXX: this has to go
290                size_t   n;
291                n = sizeof(sess->isid);
292                if(sysctlbyname("net.iscsi.isid", (void *)sess->isid, (size_t *)&n, 0, 0) != 0)
293                     perror("sysctlbyname");
294           }
295           if(ioctl(fd, ISCSISETSES, &n)) {
296                perror("ISCSISETSES");
297                return 0;
298           }
299           sleep(1);     /* XXX temporary */
300           asprintf(&dev, "%s%d", iscsidev, n);
301           nfd = open(dev, O_RDWR);
302           if(nfd < 0) {
303                perror(dev);
304                free(dev);
305                return 0;
306           }
307           free(dev);
308           close(fd);
309           sess->fd = nfd;
310
311           if(setOptions(sess, 0) != 0)
312                return -1;
313      }
314
315      if(ioctl(sess->fd, ISCSISETSOC, &sess->soc)) {
316           perror("ISCSISETSOC");
317           return 0;
318      }
319
320      return T4;
321 }
322
323 isess_t *currsess;
324
325 static void
326 trap(int sig)
327 {
328      syslog(LOG_NOTICE, "trapped signal %d", sig);
329      fprintf(stderr, "trapped signal %d\n", sig);
330
331      switch(sig) {
332      case SIGHUP:
333           currsess->flags |= SESS_DISCONNECT;
334           break;
335
336      case SIGUSR1:
337           currsess->flags |= SESS_RECONNECT;
338           break;
339
340      case SIGINT:
341      case SIGTERM:
342      default:
343           return; // ignore
344      }
345 }
346
347 static void
348 doCAM(isess_t *sess)
349 {
350      char       pathstr[1024];
351      union ccb  *ccb;
352      int        i;
353
354      if(ioctl(sess->fd, ISCSIGETCAM, &sess->cam) != 0) {
355           syslog(LOG_WARNING, "ISCSIGETCAM failed: %d", errno);
356           return;
357      }
358      debug(2, "nluns=%d", sess->cam.target_nluns);
359      /*
360       | for now will do this for each lun ...
361       */
362      for(i = 0; i < sess->cam.target_nluns; i++) {
363           debug(2, "CAM path_id=%d target_id=%d target_lun=%d",
364                 sess->cam.path_id, sess->cam.target_id, sess->cam.target_lun[i]);
365
366           sess->camdev = cam_open_btl(sess->cam.path_id, sess->cam.target_id,
367                                       sess->cam.target_lun[i], O_RDWR, NULL);
368           if(sess->camdev == NULL) {
369                syslog(LOG_WARNING, "%s", cam_errbuf);
370                debug(3, "%s", cam_errbuf);
371                continue;
372           }
373
374           cam_path_string(sess->camdev, pathstr, sizeof(pathstr));
375           debug(2, "pathstr=%s", pathstr);
376
377           ccb = cam_getccb(sess->camdev);
378           bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_relsim) - sizeof(struct ccb_hdr));
379           ccb->ccb_h.func_code = XPT_REL_SIMQ;
380           ccb->crs.release_flags = RELSIM_ADJUST_OPENINGS;
381           ccb->crs.openings = sess->op->tags;
382
383           if(cam_send_ccb(sess->camdev, ccb) < 0)
384                syslog(LOG_WARNING, "%s", cam_errbuf);
385           else
386           if((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
387                syslog(LOG_WARNING, "XPT_REL_SIMQ CCB failed");
388                // cam_error_print(sess->camdev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
389           }
390           else
391                syslog(LOG_INFO, "%s tagged openings now %d\n", pathstr, ccb->crs.openings);
392
393           cam_freeccb(ccb);
394           cam_close_device(sess->camdev);
395      }
396 }
397
398 static trans_t
399 supervise(isess_t *sess)
400 {
401      int        sig, val;
402
403      debug_called(3);
404
405      if(strcmp(sess->op->sessionType, "Discovery") == 0) {
406           sess->flags |= SESS_DISCONNECT;
407           return T9;
408      }
409
410      if(vflag)
411           printf("ready to go scsi\n");
412
413      if(setOptions(sess, SESS_FULLFEATURE) != 0)
414           return 0; // failure
415
416      if((sess->flags & SESS_FULLFEATURE) == 0) {
417           if(daemon(0, 1) != 0) {
418                perror("daemon");
419                exit(1);
420           }
421
422           openlog("iscontrol", LOG_CONS|LOG_PERROR|LOG_PID|LOG_NDELAY, LOG_KERN);
423           syslog(LOG_INFO, "running");
424
425           currsess = sess;
426           if(ioctl(sess->fd, ISCSISTART)) {
427                perror("ISCSISTART");
428                return -1;
429           }
430           doCAM(sess);
431
432      }
433      else {
434           if(ioctl(sess->fd, ISCSIRESTART)) {
435                perror("ISCSIRESTART");
436                return -1;
437           }
438      }
439
440      signal(SIGINT, trap);
441      signal(SIGHUP, trap);
442      signal(SIGTERM, trap);
443
444      sig = SIGUSR1;
445      signal(sig, trap);
446      if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
447           perror("ISCSISIGNAL");
448           return -1;
449      }
450      sess->flags |= SESS_FULLFEATURE;
451
452      sess->flags &= ~(SESS_REDIRECT | SESS_RECONNECT);
453      printf("iscontrol: supervise starting main loop\n");
454      /*
455       | the main loop - actually do nothing
456       | all the work is done inside the kernel
457       */
458      while((sess->flags & (SESS_REDIRECT|SESS_RECONNECT|SESS_DISCONNECT)) == 0) {
459           // do something?
460           // like sending a nop_out?
461           sleep(60);
462      }
463      printf("iscontrol: supervise going down\n");
464      syslog(LOG_INFO, "sess flags=%x", sess->flags);
465
466      sig = 0;
467      if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
468           perror("ISCSISIGNAL");
469      }
470
471      if(sess->flags & SESS_DISCONNECT) {
472           val = 0;
473           if(ioctl(sess->fd, ISCSISTOP, &val)) {
474                perror("ISCSISTOP");
475           }
476           sess->flags &= ~SESS_FULLFEATURE;
477           return T9;
478      }
479      else {
480           sess->flags |= SESS_INITIALLOGIN1;
481      }
482      return T8;
483 }
484
485 static int
486 handledDiscoveryResp(isess_t *sess, pdu_t *pp)
487 {
488      u_char     *ptr;
489      int        len, n;
490
491      debug_called(3);
492
493      len = pp->ds_len;
494      ptr = pp->ds;
495      while(len > 0) {
496           if(*ptr != 0)
497                printf("%s\n", ptr);
498           n = strlen((char *)ptr) + 1;
499           len -= n;
500           ptr += n;
501      }
502      return 0;
503 }
504
505 static int
506 doDiscovery(isess_t *sess)
507 {
508      pdu_t      spp;
509      text_req_t *tp = (text_req_t *)&spp.ipdu.bhs;
510
511      debug_called(3);
512
513      bzero(&spp, sizeof(pdu_t));
514      tp->cmd = ISCSI_TEXT_CMD /*| 0x40 */; // because of a bug in openiscsi-target
515      tp->F = 1;
516      tp->ttt = 0xffffffff;
517      addText(&spp, "SendTargets=All");
518      return sendPDU(sess, &spp, handledDiscoveryResp);
519 }
520
521 static trans_t
522 doLogin(isess_t *sess)
523 {
524      isc_opt_t  *op = sess->op;
525      int        status, count;
526
527      debug_called(3);
528
529      if(op->chapSecret == NULL && op->tgtChapSecret == NULL)
530           /*
531            | don't need any security negotiation
532            | or in other words: we don't have any secrets to exchange
533            */
534           sess->csg = LON_PHASE;
535      else
536           sess->csg = SN_PHASE;
537
538      if(sess->tsih) {
539           sess->tsih = 0;       // XXX: no 'reconnect' yet
540           sess->flags &= ~SESS_NEGODONE; // XXX: KLUDGE
541      }
542      count = 10; // should be more than enough
543      do {
544           debug(3, "count=%d csg=%d", count, sess->csg);
545           status = loginPhase(sess);
546           if(count-- == 0)
547                // just in case we get into a loop
548                status = -1;
549      } while(status == 0 && (sess->csg != FF_PHASE));
550
551      sess->flags &= ~SESS_INITIALLOGIN;
552      debug(3, "status=%d", status);
553
554      switch(status) {
555      case 0: // all is ok ...
556           sess->flags |= SESS_LOGGEDIN;
557           if(strcmp(sess->op->sessionType, "Discovery") == 0)
558                doDiscovery(sess);
559           return T5;
560
561      case 1:    // redirect - temporary/permanent
562           /*
563            | start from scratch?
564            */
565           sess->flags &= ~SESS_NEGODONE;
566           sess->flags |= (SESS_REDIRECT | SESS_INITIALLOGIN1);
567           syslog(LOG_DEBUG, "target sent REDIRECT");
568           return T7;
569
570      case 2: // initiator terminal error
571           return 0;
572      case 3: // target terminal error -- could retry ...
573           sleep(5);
574           return T7; // lets try
575      default:
576           return 0;
577      }
578 }
579
580 static int
581 handleLogoutResp(isess_t *sess, pdu_t *pp)
582 {
583      if(sess->flags & SESS_DISCONNECT)
584           return 0;
585      return T13;
586 }
587
588 static trans_t
589 startLogout(isess_t *sess)
590 {
591      pdu_t      spp;
592      logout_req_t *p = (logout_req_t *)&spp.ipdu.bhs;
593
594      bzero(&spp, sizeof(pdu_t));
595      p->cmd = ISCSI_LOGOUT_CMD| 0x40;
596      p->reason = BIT(7) | 0;
597      p->CID = htons(1);
598
599      return sendPDU(sess, &spp, handleLogoutResp);
600 }
601
602 static trans_t
603 inLogout(isess_t *sess)
604 {
605      if(sess->flags & SESS_RECONNECT)
606           return T18;
607      return 0;
608 }
609
610 typedef enum {
611      S1=1, S2, S3, S4, S5, S6, S7, S8
612 } state_t;
613 \f
614 #if 0
615       S1: FREE
616       S2: XPT_WAIT
617       S4: IN_LOGIN
618       S5: LOGGED_IN
619       S6: IN_LOGOUT
620       S7: LOGOUT_REQUESTED
621       S8: CLEANUP_WAIT
622
623                      -------<-------------+
624          +--------->/ S1    \<----+       |
625       T13|       +->\       /<-+   \      |
626          |      /    ---+---    \   \     |
627          |     /        |     T2 \   |    |
628          |  T8 |        |T1       |  |    |
629          |     |        |        /   |T7  |
630          |     |        |       /    |    |
631          |     |        |      /     |    |
632          |     |        V     /     /     |
633          |     |     ------- /     /      |
634          |     |    / S2    \     /       |
635          |     |    \       /    /        |
636          |     |     ---+---    /         |
637          |     |        |T4    /          |
638          |     |        V     /           | T18
639          |     |     ------- /            |
640          |     |    / S4    \             |
641          |     |    \       /             |
642          |     |     ---+---              |         T15
643          |     |        |T5      +--------+---------+
644          |     |        |       /T16+-----+------+  |
645          |     |        |      /   -+-----+--+   |  |
646          |     |        |     /   /  S7   \  |T12|  |
647          |     |        |    / +->\       /<-+   V  V
648          |     |        |   / /    -+-----       -------
649          |     |        |  / /T11   |T10        /  S8   \
650          |     |        V / /       V  +----+   \       /
651          |     |      ---+-+-      ----+--  |    -------
652          |     |     / S5    \T9  / S6    \<+    ^
653          |     +-----\       /--->\       / T14  |
654          |            -------      --+----+------+T17
655          +---------------------------+
656 #endif
657
658 int
659 fsm(isc_opt_t *op)
660 {
661      state_t    state;
662      isess_t    *sess;
663
664      if((sess = calloc(1, sizeof(isess_t))) == NULL) {
665           // boy, is this a bad start ...
666           fprintf(stderr, "no memory!\n");
667           return -1;
668      }
669
670      state = S1;
671      sess->op = op;
672      sess->fd = -1;
673      sess->soc = -1;
674      sess->target.address = strdup(op->targetAddress);
675      sess->target.port = op->port;
676      sess->target.pgt = op->targetPortalGroupTag;
677
678      sess->flags = SESS_INITIALLOGIN | SESS_INITIALLOGIN1;
679
680      do {
681           switch(state) {
682           case S1:
683                switch(tcpConnect(sess)) {
684                case T1: state = S2; break;
685                default: state = S8; break;
686                }
687                break;
688
689           case S2:
690                switch(startSession(sess)) {
691                case T2: state = S1; break;
692                case T4: state = S4; break;
693                default: state = S8; break;
694                }
695                break;
696
697           case S4:
698                switch(doLogin(sess)) {
699                case T7:  state = S1; break;
700                case T5:  state = S5; break;
701                default: state = S8; break;
702                }
703                break;
704
705           case S5:
706                switch(supervise(sess)) {
707                case T8:  state = S1; break;
708                case T9:  state = S6; break;
709                case T11: state = S7; break;
710                case T15: state = S8; break;
711                default: state = S8; break;
712                }
713                break;
714
715           case S6:
716                switch(startLogout(sess)) {
717                case T13: state = S1; break;
718                case T14: state = S6; break;
719                case T16: state = S8; break;
720                default: state = S8; break;
721                }
722                break;
723
724           case S7:
725                switch(inLogout(sess)) {
726                case T18: state = S1; break;
727                case T10: state = S6; break;
728                case T12: state = S7; break;
729                case T16: state = S8; break;
730                default: state = S8; break;
731                }
732                break;
733
734           case S8:
735                // maybe do some clean up?
736                syslog(LOG_INFO, "terminated");
737                return 0;
738           default:
739                syslog(LOG_INFO, "unknown state %d", state);
740                return 0;
741           }
742      } while(1);
743 }