Source file: /~heha/mb-iwp/BootSelektor/Firmware.zip/bootsel.c

// MAC address found in shipped EEPROM
#define MY_MAC  "\x00\x06\x98\x01\x0E\xBA"

// Unique IP address of the Ethernut Board. 
// All following defines ignored if DHCP is used.
#define MY_IPADDR "192.168.192.35"

// IP network mask of the Ethernut Board.
#define MY_IPMASK "255.255.255.0"

// Gateway IP address for the Ethernut Board.
#define MY_IPGATE "192.168.192.1"

/* Wether we should use DHCP. */
#define USE_DHCP
/* Wether we should run a discovery responder. */
#define USE_DISCOVERY
/* Wether to use PHAT file system. */

#define MY_FSDEV        devUrom

#include <cfg/os.h>

#include <stdio.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>

#include <dev/nvmem.h>		// NutNvMemSave()

#include <dev/board.h>
#include <dev/urom.h>
//#include <dev/nplmmc.h>
//#include <dev/sbimmc.h>
//#include <fs/phatfs.h>

#include <sys/version.h>
#include <sys/thread.h>
#include <sys/timer.h>
#include <sys/heap.h>
#include <sys/confnet.h>
#include <sys/socket.h>

#include <arpa/inet.h>
#include <net/route.h>

#include <pro/httpd.h>
#include <pro/dhcp.h>
//#include <pro/ssi.h>
//#include <pro/asp.h>
#include <pro/discover.h>

#ifdef NUTDEBUG
#include <sys/osdebug.h>
#include <net/netdebug.h>
#endif

#include "hardware.h"

// Gemeinsam genutzter Kopfteil
static void PutHeader(FILE *f, REQUEST *req, prog_char *title) {
 static char *html_mt = "text/html";
 static prog_char head[]=
   "<HTML><HEAD>\n"
   "<META http-equiv=content-type content=\"text/html; charset=utf-8\">\n"
   "<LINK rel=stylesheet href=\"/screen.css\">\n"
   "<LINK rel=\"shortcut icon\" href=\"/boot.ico\">\n"
   "<TITLE>%P</TITLE></HEAD>\n"
   "<BODY><H1>%P</H1>\n";
    /* Send HTTP response. */
 NutHttpSendHeaderTop(f, req, 200, "Ok");
 NutHttpSendHeaderBot(f, html_mt, -1);
 fprintf_P(f,head,title,title);
}

// Tabellenkopf ausgeben
static void PutTableHead(FILE *f, prog_char *tablehead) {
 fputs_P(PSTR("<TABLE border cellpadding=4 cellspacing=0>\n<TR>"),f);
 fputs_P(tablehead,f);
}

// Gemeinsam genutzter Fußteil
static void PutFoot(FILE *f, prog_char *endtext) {
 fputs_P(endtext,f);
 fputs_P(PSTR("</BODY></HTML>\n"),f);
 fflush(f);
}

/*******************************************
 * CGI-Prozeduren für Ethernut-Information *
 *******************************************/

/* CGI Sample: Show list of threads.
 * is called by NutHttpProcessRequest() when the client
 * request the URL 'cgi-bin/threads.cgi'.
 */
static int ShowThreads(FILE * stream, REQUEST * req) {
 static prog_char thead[] =
   "<TH>Handle<TH>Name<TH>Priorität<TH>Zustand"
   "<TH>Ereignis-<BR>warteschlange<TH>Timer<TH>Stack-<BR>pointer"
   "<TH>Freier<BR>Stack\n<TH>Status\n";
 static prog_char tfmt[] =
   "<TR><TD>%04X<TD>%s<TD>%u<TD>%P<TD>%04X<TD>%04X<TD>%04X<TD>%u<TD>%P\n";
 static prog_char tstate0[]="TRM";
 static prog_char tstate1[]="<FONT COLOR=#CC0000>RUN";
 static prog_char tstate2[]="<FONT COLOR=#339966>RDY";
 static prog_char tstate3[]="SLP";
 static prog_char *thread_states[]={tstate0,tstate1,tstate2,tstate3};
 NUTTHREADINFO *tdp = nutThreadList;

 PutHeader(stream,req,PSTR("Thread-Liste"));
 PutTableHead(stream,thead);
 for (tdp = nutThreadList; tdp; tdp = tdp->td_next) {
  fprintf_P(stream, tfmt,
    (uptr_t)tdp, tdp->td_name, tdp->td_priority,
    thread_states[tdp->td_state], (uptr_t)tdp->td_queue, (uptr_t)tdp->td_timer,
    (uptr_t)tdp->td_sp, (uptr_t)tdp->td_sp - (uptr_t)tdp->td_memory,
    *((u_long *) tdp->td_memory) != DEADBEEF ? PSTR("Stacküberlauf") : PSTR("OK"));
 }
 PutFoot(stream,PSTR("</TABLE>"));
 return 0;
}

/* CGI Sample: Show list of timers.
 * is called by NutHttpProcessRequest() when the client
 * request the URL 'cgi-bin/timers.cgi'.
 */
static int ShowTimers(FILE * stream, REQUEST * req) {
 static prog_char thead[] =
   "<TH>Handle<TH>Countdown<TH>Tick Reload"
   "<TH>Rückruf-<BR>adresse<TH>Rückruf-<BR>Argument\n";
 static prog_char tfmt[] =
   "<TR><TD>%04X<TD>%lu<TD>%lu<TD>%04X<TD>%04X\n";
 NUTTIMERINFO *tnp;
 u_long ticks_left;

 PutHeader(stream,req,PSTR("Timer-Liste"));
 PutTableHead(stream,thead);
 tnp = nutTimerList;
 ticks_left = 0;
 while (tnp) {
  ticks_left += tnp->tn_ticks_left;
  fprintf_P(stream, tfmt,
    (uptr_t)tnp,ticks_left,tnp->tn_ticks,
    (uptr_t)tnp->tn_callback,(uptr_t)tnp->tn_arg);
  tnp = tnp->tn_next;
 }
 PutFoot(stream,PSTR("</TABLE>"));
 return 0;
}

/* CGI Sample: Show list of sockets.
 * is called by NutHttpProcessRequest() when the client
 * request the URL 'cgi-bin/sockets.cgi'.
 */
static int ShowSockets(FILE * stream, REQUEST * req) {
 static prog_char thead[]=
   "<TH>Handle<TH>Typ<TH>Lokal<TH>Remote<TH>Status\n";
 static prog_char tfmt[]=
   "<TR><TD>%04X<TD>TCP<TD>%s:%u<TD>%s:%u<TD>%P\n";
 prog_char *st_P;
 extern TCPSOCKET *tcpSocketList;
 TCPSOCKET *ts;

 PutHeader(stream,req,PSTR("Socket-Liste"));
 PutTableHead(stream,thead);
 for (ts = tcpSocketList; ts; ts = ts->so_next) {
  switch (ts->so_state) {
   case TCPS_LISTEN:		st_P = PSTR("LISTEN"); break;
   case TCPS_SYN_SENT:		st_P = PSTR("SYNSENT"); break;
   case TCPS_SYN_RECEIVED:	st_P = PSTR("SYNRCVD"); break;
   case TCPS_ESTABLISHED:	st_P = PSTR("<FONT COLOR=#CC0000>ESTABL"); break;
   case TCPS_FIN_WAIT_1:	st_P = PSTR("FINWAIT1"); break;
   case TCPS_FIN_WAIT_2:	st_P = PSTR("FINWAIT2"); break;
   case TCPS_CLOSE_WAIT:	st_P = PSTR("CLOSEWAIT"); break;
   case TCPS_CLOSING:		st_P = PSTR("CLOSING"); break;
   case TCPS_LAST_ACK:		st_P = PSTR("LASTACK"); break;
   case TCPS_TIME_WAIT:		st_P = PSTR("TIMEWAIT"); break;
   case TCPS_CLOSED:		st_P = PSTR("CLOSED"); break;
   default:			st_P = PSTR("UNKNOWN"); break;
  }
//Fixed a bug reported by Zhao Weigang.
  fprintf_P(stream, tfmt,
    (uptr_t)ts, inet_ntoa(ts->so_local_addr), ntohs(ts->so_local_port),
    inet_ntoa(ts->so_remote_addr), ntohs(ts->so_remote_port), st_P);
 }
 PutFoot(stream,PSTR("</TABLE>"));
 return 0;
}

/*! \fn Service(void *arg)
 * \brief HTTP service thread.
 *
 * The endless loop in this thread waits for a client connect,
 * processes the HTTP request and disconnects. Nut/Net doesn't
 * support a server backlog. If one client has established a
 * connection, further connect attempts will be rejected.
 * Typically browsers open more than one connection in order
 * to load images concurrently. So we run this routine by
 * several threads.
 *
 */
THREAD(Service, arg)
{
    TCPSOCKET *sock;
    FILE *stream;
    u_char id = (u_char) ((uptr_t) arg);

    /*
     * Now loop endless for connections.
     */
    for (;;) {

        /*
         * Create a socket.
         */
        if ((sock = NutTcpCreateSocket()) == 0) {
            printf_P(PSTR("[%u] Creating socket failed\n"), id);
            NutSleep(5000);
            continue;
        }

        /*
         * Listen on port 80. This call will block until we get a connection
         * from a client.
         */
        NutTcpAccept(sock, 80);
        printf_P(PSTR("[%u] Connected, %u bytes free\n"), id, NutHeapAvailable());

        /*
         * Wait until at least 8 kByte of free RAM is available. This will
         * keep the client connected in low memory situations.
         */
        while (NutHeapAvailable() < 8192) {
            printf_P(PSTR("[%u] Low mem\n"), id);
            NutSleep(1000);
        }

        /*
         * Associate a stream with the socket so we can use standard I/O calls.
         */
        if ((stream = _fdopen((int) ((uptr_t) sock), "r+b")) == 0) {
            printf_P(PSTR("[%u] Creating stream device failed\n"), id);
        } else {
            /*
             * This API call saves us a lot of work. It will parse the
             * client's HTTP request, send any requested file from the
             * registered file system or handle CGI requests by calling
             * our registered CGI routine.
             */
            NutHttpProcessRequest(stream);

            /*
             * Destroy the virtual stream device.
             */
            fclose(stream);
        }

        /*
         * Close our socket.
         */
        NutTcpCloseSocket(sock);
        printf_P(PSTR("[%u] Disconnected\n"), id);
    }
}

/***********************
 * CGI-Hilfsfunktionen *
 ***********************/
 
enum {false, true};

// Schreibt nur die veränderten Bytes (Geschwindigkeit!)
// Liefert die Anzahl der geänderten Bytes (Änderungskontrolle!)
static size_t EeWriteChanged(unsigned EeAdr, u_char*MemAdr, size_t Len) {
 u_char b;
 size_t ret=0;
 for (;Len; EeAdr++,MemAdr++,Len--) {
  NutNvMemLoad(EeAdr,&b,1);
  if (b!=*MemAdr) {
   NutNvMemSave(EeAdr,MemAdr,1);
   ret++;
  }
 }
 return ret;
}

// Schreibt eine (einzeilige) Nachricht in (nur Mozilla: abgerundeter) Box 
static void PutMessage(FILE*f,const prog_char*format,...) {
 va_list va;	// Variable Argumentliste
 fputs_P(PSTR("<P class=notice>"),f);
 va_start(va,format);
 vfprintf_P(f,format,va);
 va_end(va);
 fputs_P(PSTR("</P>\n"),f);
}

// Wie strlcpy, aber bei "ganzen" UTF-8-Zeichen kürzen
static void strlcpy_utf8(char *d, const char*s, size_t bufsize) {
 u_char c;			// Zu betrachtendes Führungsbyte
 u_char l;			// Anzahl Bytes eines UTF-8-kodierten Zeichens
 if (!bufsize) return;
 c = *s++;			// Zeichen oder Führungsbyte laden
 for (;;) {
  if (!c) break;
  l = 1;
  if (c >= 0xF0) l++;
  if (c >= 0xE0) l++;
  if (c >= 0xC0) l++;
  if (l >= bufsize) break;	// Der "="-Fall bewahrt Platz für '\0'
  bufsize -= l;			// verbleibender Platz
  do{
   *d++ = c;			// Ganzes Multibyte-Zeichen kopieren
   c = *s++;
  }while (--l);
 }
 *d = 0;
}

/*********************************
 * CGI-Prozedur zur Boot-Auswahl *
 *********************************/
#define NUMPORTS 16		// Dieses Gerät hat 16 Ports (Buchsen)
// (Eine ähnliche Software könnte mal massiv-viele Geräte steuern)
#define MAXGROUPS 16		// hier: maximal ist jede Buchse eine Gruppe
#define STRMAXLEN 15		// Maximale Länge für alle Strings
// Boot-Gruppen-Eigenschaften (so auch im EEPROM, max. 16 Gruppen)
typedef char string_t[STRMAXLEN+1];

typedef union{
 struct{
  string_t ports;	// Ports, bspw. "1-10" oder "7,9"
  string_t groupname;	// Gruppen-Name, bspw. "Cave"
  string_t bootname[4];	// Betriebssystem-Namen, bspw. "XP SP2", "Suse 7.8"...
 };
 string_t names[6];	// alle Namen sind 16 Zeichen lang, das ist alles!
}bootgroup_t;

#define BOOTGROUP_EE_OFFSET 0x90

// Prüft die übergebene Liste auf Korrektheit, liefert Maske <>0 wenn OK
static unsigned GroupCheck(const char*ports) {
 unsigned ret=0;
 char c=*ports;
 while (c) {
  u_char e,a=strtoul(ports,(char**)&ports,10);	// Anfang lesen
  if (a==0 || a>NUMPORTS) return 0;	// Nicht mehr als Anzahl Ports
  e=a;
  c=*ports++;
  switch (c) {
   case '-': {
    e=strtoul(ports,(char**)&ports,10);		// Ende lesen
    if (e<=a || e>NUMPORTS) return 0;	// Ende muss größer als Anfang sein
    c=*ports++;
   }break;
   case ',':				// Komma als Trenner erlaubt
   case ' ':				// Leerzeichen ebenfalls erlaubt
   case 0: break;
   default: return 0;			// jedes andere Zeichen = Fehler
  }
  a--;
  do ret|=1U<<a; while (++a<e);
 }
 return ret;
}

// Setzt die Ports einer Gruppe; die Port-Liste wird als korrekt angenommen
static void GroupSetBootsel(const char *ports, u_char sel) {
 unsigned m, mask;
 u_char i;
 if (sel>4) return;
 mask = GroupCheck(ports);
 for (i=1,m=1; i<=16; i++,m<<=1) if (mask&m) OutSetBootsel(i,sel);
}

// Liefert den Schaltzustand einer Gruppe,
// liefert 0xFF wenn <ports> nicht in Ordnung
// liefert 0xFE wenn verschiedene Zustände
static u_char GroupGetBootsel(const char *ports) {
 unsigned m, mask;
 u_char i, ret = 0xFF;
 mask = GroupCheck(ports);
 for (i=1,m=1; i<=16; i++,m<<=1) if (mask&m) {
  if (ret==0xFF) ret=OutGetBootsel(i);	// beim ersten Auffinden laden
  else if (ret!=OutGetBootsel(i)) {	// beim weiteren Auffinden vergleichen
   ret=0xFE;
   break;
  }
 }
 return ret;
}

// Liefert " checked" wenn column==state, sonst ""
static const prog_char* GetCheck(u_char column, u_char state) {
 static prog_char checked[] = " checked";
 static prog_char unchecked[] = "";
 return column==state ? checked : unchecked;
}

// CGI-Handler
static int EditBootsel(FILE*f, REQUEST*req) {
 static prog_char thead[] =
   "<TH> <TH>Buchse<small><br>(Buchsengruppe)"
   "<TH>Rechnername<small><br>(Gruppenname)"
   "<TH colspan=5>Boot-Vorauswahl\n";
 static prog_char same[] = ": <INPUT type=radio name=";
 static prog_char tfmt[] =
   " <TR><TH>%u:<TH>%s\n"
   "  <TH>%s\n"
   "  <TD>0%P%u%P value=0>keine</INPUT>\n"
   "  <TD bgcolor=#FFC0C0>1%P%u%P value=1>%s</INPUT>\n"
   "  <TD bgcolor=#FFFFC0>2%P%u%P value=2>%s</INPUT>\n"
   "  <TD bgcolor=#C0FFC0>3%P%u%P value=3>%s</INPUT>\n"
   "  <TD bgcolor=#FFE0C0>4%P%u%P value=4>%s</INPUT>\n";
 bootgroup_t *Groups;
 u_char i, values[MAXGROUPS];

 PutHeader(f,req,PSTR("Boot-Auswahl"));
 Groups = NutHeapAlloc(sizeof(bootgroup_t)*MAXGROUPS);
 if (Groups) {
  NutNvMemLoad(BOOTGROUP_EE_OFFSET,Groups,sizeof(bootgroup_t)*MAXGROUPS);
// Schaltzustände lesen
  for (i=0; i<MAXGROUPS; i++) {
   if ((u_char)Groups[i].ports[0]==0xFF) break;
   values[i]=GroupGetBootsel(Groups[i].ports);
  }
// CGI-Daten auswerten und ggf. Message einfügen 
  if (req->req_query) {
   u_char i;
   u_char key, val;
   char *pk, *pv, **args = req->req_qptrs;
#if 0
  PutMessage(f,PSTR("CGI-Daten sind da"));
  fprintf_P(f,PSTR("<BR>num=%u<P>\n"),req->req_numqptrs);
  {int i;
   char **args = req->req_qptrs;
   for (i=0; i<req->req_numqptrs*2; i++) {
    fprintf_P(f,PSTR("%s<br>\n"),*args++);
   }
  }
#endif
   for (i=0; i<req->req_numqptrs; i++) {
    key = strtoul(*args++,&pk,10)-1;	// Gruppen-Zeilennummer (war 1-basiert)
    val = strtoul(*args++,&pv,10);	// Radiobuttons 0..4
    if (*pk || *pv || key>=MAXGROUPS
    || (u_char)Groups[key].ports[0]==0xFF || val>4) {
     PutMessage(f,PSTR("Ungültiger Formularaufruf!"));
     goto raus;
    }
    values[key]=val;
   }
   for (i=0; i<MAXGROUPS; i++) {
    if ((u_char)Groups[i].ports[0]==0xFF) break;
    GroupSetBootsel(Groups[i].ports,values[i]);
   }
   PutMessage(f,PSTR("Boot-Auswahl gesetzt"));
  }
raus:
// Webseite mit Istwerten (ggf. indifferenten Werten) aufbauen
  if ((u_char)Groups[0].ports[0]==0xFF) {
   PutFoot(f,PSTR("Keine Gruppen definiert!\n"
     "Gehen Sie zur <A href=\"bootsgl.cgi\">Boot-Auswahl (Einzelport)</A>\n"
     "oder zur <A href=\"groups.cgi\">Gruppen-Festlegung</A>!\n"));
  }else{
   fputs_P(PSTR("<FORM action=\"bootsel.cgi\">\n"),f);
   PutTableHead(f,thead);
   for (i=0; i<MAXGROUPS; i++) {
    if ((u_char)Groups[i].ports[0] == 0xFF) break;
    fprintf_P(f,tfmt,		// Tabellenzeile ausgeben
      i+1,
      Groups[i].ports,
      Groups[i].groupname,
      same,i+1,GetCheck(0,values[i]),
      same,i+1,GetCheck(1,values[i]),Groups[i].bootname[0],
      same,i+1,GetCheck(2,values[i]),Groups[i].bootname[1],
      same,i+1,GetCheck(3,values[i]),Groups[i].bootname[2],
      same,i+1,GetCheck(4,values[i]),Groups[i].bootname[3]);
   }
   PutFoot(f,PSTR(
     "</TABLE>\n"
    "<P><INPUT type=submit value=\"Setzen\">  \n"
    "<INPUT type=button value=\"Aktualisieren\" onclick=\"window.location.href='bootsel.cgi'\"></P>\n"
     "</FORM>\n"));
  }
  NutHeapFree(Groups);
 }else PutFoot(f,PSTR("Zu wenig RAM für Verarbeitung!"));
 return 0;
}

// CGI-Handler für einzelportweise Ausgabe
static int EditBootsgl(FILE*f, REQUEST*req) {
 static prog_char thead[] =
   "<TH>Buchse"
   "<TH>Rechnername<small><br>(Gruppenname)"
   "<TH colspan=5>Boot-Vorauswahl\n";
 static prog_char same[] = ": <INPUT type=radio name=";
 static prog_char tfmt[] =
   " <TR><TH>%u:\n"
   "  <TH>%s\n"
   "  <TD>0%P%u%P value=0>keine</INPUT>\n"
   "  <TD bgcolor=#FFC0C0>1%P%u%P value=1>%s</INPUT>\n"
   "  <TD bgcolor=#FFFFC0>2%P%u%P value=2>%s</INPUT>\n"
   "  <TD bgcolor=#C0FFC0>3%P%u%P value=3>%s</INPUT>\n"
   "  <TD bgcolor=#FFE0C0>4%P%u%P value=4>%s</INPUT>\n";
 bootgroup_t *Groups;
 u_char i, j, members[NUMPORTS], values[NUMPORTS];

 PutHeader(f,req,PSTR("Boot-Auswahl"));
 Groups = NutHeapAlloc(sizeof(bootgroup_t)*MAXGROUPS);
 if (Groups) {
  NutNvMemLoad(BOOTGROUP_EE_OFFSET,Groups,sizeof(bootgroup_t)*MAXGROUPS);
// Zugehörigkeit Port->Gruppe finden
  memset(members,0xFF,sizeof(members));
  memset(values,0xFF,sizeof(values));
  for (i=0; i<MAXGROUPS; i++) {
   unsigned m, mask;
   if ((u_char)Groups[i].ports[0] == 0xFF) break;
   mask = GroupCheck(Groups[i].ports);
   for (j=0,m=1; j<NUMPORTS; j++,m<<=1) if (mask&m) members[j]=i;
  }
// CGI-Daten auswerten und ggf. Message einfügen 
  if (req->req_query) {
   u_char i;
   u_char key, val;
   char *pk, *pv, **args = req->req_qptrs;
   for (i=0; i<req->req_numqptrs; i++) {
    key = strtoul(*args++,&pk,10)-1;	// Portnummer (war 1-basiert)
    val = strtoul(*args++,&pv,10);	// Radiobuttons 0..4
    if (*pk || *pv || key>=NUMPORTS || val>4) {
     PutMessage(f,PSTR("Ungültiger Formularaufruf!"));
     goto raus;
    }
    values[key]=val;
   }
   for (i=0; i<NUMPORTS; i++) {
    if (values[i]<=4) OutSetBootsel(i+1,values[i]);
   }
   PutMessage(f,PSTR("Boot-Auswahl gesetzt"));
  }
raus:
// Webseite mit Istwerten aufbauen
  fputs_P(PSTR("<FORM action=\"bootsgl.cgi\">\n"),f);
  PutTableHead(f,thead);
  for (i=0; i<NUMPORTS; i++) {
   bootgroup_t *member = NULL;
   if (members[i]<MAXGROUPS) member = Groups+members[i];
   j = OutGetBootsel(i+1);
   fprintf_P(f,tfmt,		// Tabellenzeile ausgeben
     i+1,
     member?member->groupname:"–",
     same,i+1,GetCheck(0,j),
     same,i+1,GetCheck(1,j),member?member->bootname[0]:"??",
     same,i+1,GetCheck(2,j),member?member->bootname[1]:"??",
     same,i+1,GetCheck(3,j),member?member->bootname[2]:"??",
     same,i+1,GetCheck(4,j),member?member->bootname[3]:"??");
  }
  PutFoot(f,PSTR(
    "</TABLE>\n"
    "<P><INPUT type=submit value=\"Setzen\">  \n"
    "<INPUT type=button value=\"Aktualisieren\" onclick=\"window.location.href='bootsgl.cgi'\"></P>\n"
    "</FORM>\n"));
  NutHeapFree(Groups);
 }else PutFoot(f,PSTR("Zu wenig RAM für Verarbeitung!"));
 return 0;
}


/********************************************
 * CGI-Prozedur zur Boot-Gruppen-Verwaltung *
 ********************************************/

// Auswertung der CGI-Daten, füllt <Groups> und schreibt EEPROM wenn OK
// Rückkehr-Kodes: 0 = keine Daten verändert
//	     positiv = Anzahl Bytes im EEPROM verändert
//		  -1 = Syntaxfehler (*el = Zeilennummer)
//		  -2 = überlappende Ports (*el = Zeilennummer)
//		  -3 = zu viele Gruppen
static int ProcessGroupsCgi(REQUEST *req, u_char *el, bootgroup_t *Groups) {
 char *key, *val, **args = req->req_qptrs;
// bootgroup_t *CurGroup=NutHeapAlloc(sizeof(bootgroup_t));
 unsigned Ports, PortsUsed = 0;
 u_char i,j;
 u_char NumGroups = 0, LineFilled = false;
 int ret=0;
 memset(Groups,0xFF,sizeof(bootgroup_t)*MAXGROUPS);
 for (j = 0; j < req->req_numqptrs; j++) {
  key = *args++;
  val = *args++;
  switch (i = *key++) {
   case 'P':
   case 'p': {		// Port-Liste: neue Tabellenzeile beginnt
    if (NumGroups == MAXGROUPS) {
     ret = -3;		// Fehler-Kode: Zu viele Gruppen
     goto exi;
    }
    if (*val) {	// gefüllte Zeile
     Ports=GroupCheck(val);
     if (!Ports && !ret) {
      ret = -1;		// Fehler-Kode: Syntaxfehler
      *el = NumGroups+1;
     }
     if (PortsUsed&Ports && !ret) {
      ret = -2;		// Fehler-Kode: Überlappende Gruppe
      *el = NumGroups+1;
     }
     PortsUsed|=Ports;			// Verwendete Ports vermerken
     LineFilled++;
     i=0; goto copy;
    }
   }break;
   case 'N':
   case 'n': {		// Gruppen-Name
    i=1; goto copy;
   }break;
   case '1':		// Vier Boot-Namen
   case '2':
   case '3':
   case '4': {
    i += -'1' + 2;
copy:
    if (LineFilled) {
     strlcpy_utf8(Groups[NumGroups].names[i],val,16);
     if (i == 5) {		// Letzter Boot-Name?
      NumGroups++;
      LineFilled = false;
     }
    }
   }
  }
 }
 if (NumGroups<MAXGROUPS) NumGroups++;
 if (!ret) ret=EeWriteChanged(BOOTGROUP_EE_OFFSET,
   (void*)Groups,sizeof(bootgroup_t)*NumGroups);
exi:
 return ret;
}

// CGI-Handler
static int EditGroups(FILE*f, REQUEST*req) {
 static prog_char thead[] =
   "<TH> <TH>Buchse<small><br>(Buchsengruppe)"
   "<TH>Rechnername<small><br>(Gruppenname)"
   "<TH colspan=4>Betriebssystem-Namen\n";
 static prog_char editsize[] = " size=16 maxlen=15 value=\"";
 static prog_char tfmt[] =
   " <TR><TH>%u:<TD><INPUT name=p%P%s\">\n"
   "  <TD><INPUT name=n%P%s\">\n"
   "  <TD bgcolor=#FFC0C0>1: <INPUT name=1%P%s\">\n"
   "  <TD bgcolor=#FFFFC0>2: <INPUT name=2%P%s\">\n"
   "  <TD bgcolor=#C0FFC0>3: <INPUT name=3%P%s\">\n"
   "  <TD bgcolor=#FFE0C0>4: <INPUT name=4%P%s\">\n";

 bootgroup_t *Groups;
 u_char i;

 PutHeader(f,req,PSTR("Gruppen-Festlegung"));
 Groups = NutHeapAlloc(sizeof(bootgroup_t)*MAXGROUPS);
 if (Groups) {
// CGI-Daten auswerten und ggf. Message einfügen
  if (req->req_method == METHOD_POST) NutHttpProcessPostQuery(f,req);
  if (req->req_query) {
   u_char el=0;					// el = Error-Location
   int ec=ProcessGroupsCgi(req,&el,Groups);	// ec = Error-Code
   switch (ec) {
    case -1: PutMessage(f,PSTR("Syntaxfehler bei Buchsenbezeichnung in Zeile %u!"),el); break;
    case -2: PutMessage(f,PSTR("Überlappende Buchsenbezeichnungen in Zeile %u!"),el); break;
    case -3: PutMessage(f,PSTR("Zu viele Gruppen!")); break;
    case 0: break;			// keine Message
    default: PutMessage(f,PSTR("Geänderte Daten permanent gepeichert, %u Bytes"),ec);
   }
  }else{	// Daten aus EEPROM lesen
   NutNvMemLoad(BOOTGROUP_EE_OFFSET,Groups,sizeof(bootgroup_t)*MAXGROUPS);
  }
// Webseite mit Istwerten oder eingegebenen (falschen) Werten aufbauen
  fputs_P(PSTR("<FORM action=\"groups.cgi\" method=post>\n"),f);
  PutTableHead(f,thead);
  for (i=0; i<MAXGROUPS; i++) {
   if ((u_char)Groups[i].ports[0] == 0xFF) break;
   fprintf_P(f,tfmt,		// Tabellenzeile ausgeben
     i+1,
     editsize,Groups[i].ports,
     editsize,Groups[i].groupname,
     editsize,Groups[i].bootname[0],
     editsize,Groups[i].bootname[1],
     editsize,Groups[i].bootname[2],
     editsize,Groups[i].bootname[3]);
  }
  if (i<MAXGROUPS) {
   fprintf_P(f,tfmt,		// leere Tabellenzeile am Ende ausgeben
     i+1,
     editsize,"",
     editsize,"",
     editsize,"",
     editsize,"",
     editsize,"",
     editsize,"");
  }
  PutFoot(f,PSTR(
    "</TABLE>\n"
    "<P><INPUT type=submit value=\"Setzen\"></P>\n"
    "</FORM>\n"
    "Buchsen-Nummern: 1..16<BR>\n"
    "Buchsengruppen wie bei <code>psselect</code>.<BR>\n"
    "Leere Feld bei „Buchse“ = Zeile löschen<P>\n"
    "Bei „Rechnername“ sind beliebige Namen einzutragen.\n"
    "Diese sieht der Benutzer bei „Boot-Auswahl“\n"));
  NutHeapFree(Groups);
 }else PutFoot(f,PSTR("Zu wenig RAM für Verarbeitung!"));
 return 0;
}

/**************************************
 * CGI-Prozedur zur Zugriffskontrolle *
 **************************************/
#define PASSWORD_EE_OFFSET BOOTGROUP_EE_OFFSET + MAXGROUPS*sizeof(bootgroup_t)
typedef struct {
 string_t password;
// später noch: IP-Adressbereich
}accesscontrol_t;

static void RegisterAuth(void) {
 char catbuf[5+STRMAXLEN] = "root:ethernut";
 u_char b;
 NutNvMemLoad(PASSWORD_EE_OFFSET,&b,1);
 if (b != 0xFF) {
  NutNvMemLoad(PASSWORD_EE_OFFSET,catbuf+5,STRMAXLEN+1);
 }
 if (b) NutRegisterAuth("cgi-bin", catbuf);
}
 
// CGI: Passwort-Eingabe
static int EditPassword(FILE *f, REQUEST *req) {
 static prog_char editsize[] = " type=password size=16 maxlen=15 value=\"";
 accesscontrol_t *AC;
 char *pass1 = NULL, *pass2 = NULL;
 
 PutHeader(f,req,PSTR("Zugriffskontrolle"));
// CGI-Daten auswerten und ggf. Message einfügen 
 AC = NutHeapAlloc(sizeof(accesscontrol_t));
 if (AC) {
  if (req->req_query) {
   int i;
   char *key, *val, **args = req->req_qptrs;
   for (i=0; i<req->req_numqptrs; i++) {
    key = *args++;
    val = *args++;
    if (!strcasecmp_P(key,PSTR("pass1"))) pass1 = val;
    else if (!strcasecmp_P(key,PSTR("pass2"))) pass2 = val;
   }
   if (!pass1 || !pass2) PutMessage(f,PSTR("Ungültiger Formularaufruf!"));
   else if (strcmp(pass1,pass2)) PutMessage(f,PSTR("Passwörter unterschiedlich!"));
   else{
    u_char len = strlen(pass1);
    if (len>STRMAXLEN) PutMessage(f,PSTR("Passwort zu lang (%u Bytes)!"),len);
    else{
     EeWriteChanged(PASSWORD_EE_OFFSET,(u_char*)pass1,len+1);
     NutClearAuth();
     RegisterAuth();
     PutMessage(f,PSTR("Passwort gesetzt"));
    }
   }
  }else{
   NutNvMemLoad(PASSWORD_EE_OFFSET,AC,sizeof(accesscontrol_t));
   if ((u_char)AC->password[0] == 0xFF) strcpy_P(AC->password,PSTR("ethernut"));
   pass1 = pass2 = AC->password;
  }
// Webseite mit Istwerten oder eingegebenen (falschen) Werten aufbauen
  fputs_P(PSTR("<FORM action=\"password.cgi\">\n"),f);
  PutTableHead(f,PSTR("<TD>login:<TD><INPUT value=root size=16 disabled>\n"));
  fprintf_P(f,PSTR(
    "<TR><TD rowspan=2>Password<TD><INPUT name=pass1%P%s\">\n"
    "<TR><TD><INPUT name=pass2%P%s\">\n"),
    editsize,pass1,
    editsize,pass2);
  PutFoot(f,PSTR(
    "</TABLE>\n"
    "<P><INPUT type=submit value=\"Setzen\"></P>\n"
    "</FORM>\n"));
  NutHeapFree(AC);
 }else PutFoot(f,PSTR("Zu wenig RAM für Verarbeitung!"));
 return 0;
}

/********************
 * Telnet-Interface *
 ********************/

// Liefert Eingabe-Abbruch-Zeichen
static char EnterLine(FILE *f, const prog_char *prompt, char *buf, size_t bufsize) {
 int c;
 char *p = buf, *e = buf+bufsize-1;
 if (prompt) {
  fputs_P(prompt,f);
  fputc(' ',f);
  fflush(f);
 }
 for (;;) {
  c = fgetc(f);
  if (c == EOF) break;
  switch ((char)c) {
   case '\r': continue;
   case '\n':
   case 3:   goto exi;
   default: {
    fflush(f);
    if (p!=e) *p++=c; 
   }
  }
 }
exi:
 fputs_P(PSTR("\r"),f);
 *p = 0;
 return (char)c;
}

// Telnet-Behandlung (mit Login und Passwort?)
static void ProcessTelnet(FILE *f) {
 struct _{
  string_t login;
  string_t passwd;
  char s[128];
 }*p = NutHeapAlloc(sizeof(struct _));	// kein Platz auf Stack!!

 if (!p) return;
 NutNvMemLoad(PASSWORD_EE_OFFSET,p->s,16);
 do{
  fputs_P(PSTR("BootSelektor-RJ45 Telnet-Interface\r\n"),f);
  if (EnterLine(f,PSTR("login:"),p->login,sizeof(p->login))==3) goto exi;
  if (*p->s) {
   if (EnterLine(f,PSTR("Password:"),p->passwd,sizeof(p->passwd))==3) goto exi;
  }else p->passwd[0] = 0;
 }while (strcmp_P(p->login,PSTR("root")) || strcmp(p->passwd,p->s));
 
 fputs_P(PSTR("\np <Nummer> <Auswahl>	- Setze Einzelport auf Bootauswahl\r\n"),f);
 fputs_P(PSTR( "p <Name> <Auswahl>	- Setze Gruppe auf Bootauswahl\r\n"),f);
 fputs_P(PSTR( "r <Nummer/Name>		- Abfrage der Bootauswahl\r\n"),f);
 fputs_P(PSTR( "a - Advise, u - Unadvise\r\n"),f);
 fflush(f);
 while (EnterLine(f,NULL,p->s,sizeof(p->s))!=3) {
  switch (p->s[0]) {
   case 'p': {
    char *key, *val, *q;
    u_char nkey, nval;
    key = p->s+2;
    val = strchr(key,p->s[1]);
    if (!val) break;
    *val++ = 0;
    nkey = strtoul(key,&q,10);
//    if (*q) nkey = 0;	// keine numerische Portnummer
    if (nkey > NUMPORTS) nkey = 0;
    nval = val[0]-'0';
    if (nval>4 || val[1]) nval=0xFF;	// keine numerische Auswahl
// Hier nur numerisches Poke:
    if (nkey && nval<=4) {
     OutSetBootsel(nkey,nval);
     fprintf_P(f,PSTR("%s OK"),p->s);
    }else{
     fputs_P(PSTR("Error"),f);
    }
   }break;
   
   case 'r': {
    u_char nkey, nval;
    nkey = strtoul(p->s+2,NULL,10);
    if (nkey > NUMPORTS) nkey = 0;
    if (nkey) {
     nval = OutGetBootsel(nkey);
     fprintf_P(f,PSTR("%s %u"),p->s, nval);
    }else{
     fputs_P(PSTR("Error"),f);
    }
   }break;
  }
  fputs_P(PSTR("\r\n\n"),f);
  fflush(f);
 }
exi:
 NutHeapFree(p);
}

/*****************
 * Hauptprogramm *
 *****************/
void __attribute__((noreturn)) main(void) {
 u_long baud = 115200;
 u_char i;

 OutInit();
 OutKnightrider();
 OutRestore();

// Initialize the uart device.
 NutRegisterDevice(&DEV_DEBUG, 0, 0);
 freopen(DEV_DEBUG_NAME, "w", stdout);
 _ioctl(_fileno(stdout), UART_SETSPEED, &baud);
 NutSleep(200);
 printf_P(PSTR("\n\nNut/OS %s HTTP Daemon..."), NutVersionString());
#ifdef NUTDEBUG
 NutTraceTcp(stdout, 0);
 NutTraceOs(stdout, 0);
 NutTraceHeap(stdout, 0);
 NutTracePPP(stdout, 0);
#endif
// Register Ethernet controller.
 if (NutRegisterDevice(&DEV_ETHER, 0, 0)) {
  puts_P(PSTR("Registering device failed"));
 }

 printf_P(PSTR("Configure %s..."), DEV_ETHER_NAME);
 if (NutNetLoadConfig(DEV_ETHER_NAME)) {
  u_char mac[] = MY_MAC;

  printf_P(PSTR("initial boot..."));
  if (NutDhcpIfConfig(DEV_ETHER_NAME, mac, 60000)) {
   u_long ip_addr = inet_addr(MY_IPADDR);
   u_long ip_mask = inet_addr(MY_IPMASK);
   u_long ip_gate = inet_addr(MY_IPGATE);

   printf_P(PSTR("No DHCP..."));
   if (NutNetIfConfig(DEV_ETHER_NAME, mac, ip_addr, ip_mask) == 0) {
                /* Without DHCP we had to set the default gateway manually.*/
    if (ip_gate) {
     printf_P(PSTR("hard coded gate..."));
     NutIpRouteAdd(0, 0, ip_gate, &DEV_ETHER);
    }
    puts_P(PSTR("OK"));
   }else{
    puts_P(PSTR("failed"));
   }
  }
 }else{
  if (NutDhcpIfConfig(DEV_ETHER_NAME, 0, 60000)) puts_P(PSTR("failed"));
  else puts_P(PSTR("OK"));
 }
 printf_P(PSTR("%s ready\n"), inet_ntoa(confnet.cdn_ip_addr));

 NutRegisterDiscovery((u_long)-1, 0, DISF_INITAL_ANN);

// Register our device for the file system.
 NutRegisterDevice(&MY_FSDEV, 0, 0);

// CGI-Seiten registrieren
 NutRegisterCgi("bootsel.cgi", EditBootsel);
 NutRegisterCgi("bootsgl.cgi", EditBootsgl);
 NutRegisterCgi("groups.cgi", EditGroups);
 NutRegisterCgi("password.cgi", EditPassword);

 NutRegisterCgi("threads.cgi", ShowThreads);
 NutRegisterCgi("timers.cgi", ShowTimers);
 NutRegisterCgi("sockets.cgi", ShowSockets);

 RegisterAuth();	// Verzeichnis "cgi-bin" schützen

// Start server threads.
 for (i = 1; i <= 6; i++) {
  static char thname[] = "httpd-";
  thname[5] = '0' + i;
  NutThreadCreate(thname, Service, (void *) (uptr_t) i, NUT_THREAD_MAINSTACK);
 }

 for (;;) {
  TCPSOCKET *sock;
  FILE *f;
  sock = NutTcpCreateSocket();
  if (sock) {
   if (!NutTcpAccept(sock,23)) {
    printf_P(PSTR("Telnet client %s connected\n"),inet_ntoa(sock->so_remote_addr));
    f = _fdopen((int)sock,"r+b");
    if (f) {
     ProcessTelnet(f);
     puts_P(PSTR("Telnet client disconnected\n"));
     fclose(f);
    }else puts_P(PSTR("Error: no file\n"));
   }else puts_P(PSTR("Error: no accept\n"));
   NutTcpCloseSocket(sock);
  }else puts_P(PSTR("Error: no sock\n"));
 }
}
Detected encoding: UTF-80