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