#include #include #include #include #include #include #include #include #include #include #include #include #include #include "internal.h" #include "dialog.h" #include "diadef.h" #include "cmdsock.h" #include "dialog.m" #include "../paths.h" #include #include static HELP_FILE help_notfound ("dialog","notfound"); static char html_key[50]; // Used to encode the URL // Normally contain /html: // but may contain /htmlmod:module_key: static char *html_host = NULL; static int html_cli; // Handle use to talk back to client static int port; static int target_level; static int history_level; static int cut_level; static bool cut_info_set; // Some information has been assigned to // tblevel[cut_level]. static int debug; static HTML_VARVAL *curvars, *other_vars; static MENU_STATUS html_submit; // Button action to process static int html_postdone; // Did the POST was processed static int html_drawdone; // Insure that we draw a html page only once static bool html_popup=false; struct LEVEL_INFO{ MENU_STATUS status; SSTRING key; SSTRING title; }; static const int MAX_WWW_LEVEL=20; static LEVEL_INFO tblevel[MAX_WWW_LEVEL]; static int level = 0; /* Return the dialog depth */ int html_getlevel() { return level; } static const char *html_getval (int lev, const char *key) { char bufkey[200]; snprintf (bufkey,sizeof(bufkey)-1,"%d_%s",lev,key); const char *ret = ""; bool exist = false; if (curvars != NULL) ret = curvars->getval(bufkey,exist); if (!exist && other_vars != NULL){ ret = other_vars->getval(bufkey,exist); } return ret; } /* Get the current socket handle to talk to the client */ EXPORT int html_gethandle() { return html_cli; } /* Get the new value of a field */ EXPORT const char *html_getval (const char *key) { return html_getval (level,key); } /* Check if a button exist as a variable (the user has clicked on it) */ static int html_butexist (const char *key) { int ret = 0; if (curvars != NULL) ret = curvars->exist(key); return ret; } static void html_reset() { // Variable are simply never forgotten. Some primitive form // of garbage collection in varval.c take care of too old entries curvars = NULL; html_postdone = 0; html_drawdone = 0; html_popup=false; } /* Get the original value of a field */ const char *html_getoldval (const char *key) { char oldkey[200]; snprintf (oldkey,sizeof(oldkey)-1,"CUR_%s",key); return html_getval (oldkey); } /* Transforme/Escape some characters so they do not confuse the browser */ void html_stresc (char *dst, const char *src, int size) { char *start = dst; size -= 6; // The longuest escape we put at once while (*src != '\0' && (int)(dst-start) \n" ,type,level,name,value_esc,opt); } /* Generate a \n" ,level,name,value_esc); delete []value_esc; } void html_defvarcur ( const char *name, int value) { char tmp[20]; sprintf (tmp,"%d",value); html_defvarcur (name,tmp); } static char *html_buf; static int html_len; static int html_maxlen; static const int httpmaxsize=40000; /* Add to the buffer which will be transmitted to the browser. html_flush() should be called once all the html_printf have been done. Return how many chars added to the buffer. */ EXPORT int html_printf (const char *ctl, ...) { va_list list; va_start (list,ctl); char buf[20000]; int len = vsnprintf (buf,sizeof(buf)-1,ctl,list); va_end (list); if (html_len + len > html_maxlen){ html_maxlen += 5000; html_buf = (char*)realloc(html_buf,html_maxlen); } strcpy (html_buf+html_len,buf); html_len += len; return len; } static void html_write(const char *buf, int len) { if (buf != html_buf) html_flush(); write (html_cli,buf,len); //if (debug) write (2,buf,len); } void html_flush () { if (html_len > 0){ html_write (html_buf,html_len); html_len = 0; } } /* Record the host name and port number to encode in URLs */ void html_sethost (const char *_hostname, int _port) { free (html_host); html_host = strdup(_hostname); port = _port; } static const char KEY_ACCEPT[]="accept"; static const char KEY_DEL[]="del"; static const char KEY_INS[]="ins"; static const char KEY_OK[]="ok"; static const char KEY_ADD[]="add"; static const char KEY_EDIT[]="edit"; static const char KEY_YES[]="yes"; static const char KEY_NO[]="no"; static const char KEY_MORE[]="more"; static const char KEY_USR1[]="usr1"; static const char KEY_USR2[]="usr2"; static const char KEY_USR3[]="usr3"; static const char KEY_CUT[]="cut"; /* Format a path to a sub-meny entry */ static void html_setpath_level(char *path, int upto) { char *pt = path; LEVEL_INFO *ptl = tblevel; for (int i=0; istatus == MENU_DEL){ key = KEY_DEL; }else if (ptl->status == MENU_ACCEPT){ key = KEY_ACCEPT; }else if (ptl->status == MENU_ADD){ key = KEY_ADD; }else if (ptl->status == MENU_EDIT){ key = KEY_EDIT; }else if (ptl->status == MENU_YES){ key = KEY_YES; }else if (ptl->status == MENU_NO){ key = KEY_NO; }else if (ptl->status == MENU_MORE){ key = KEY_MORE; }else if (ptl->status == MENU_USR1){ key = KEY_USR1; }else if (ptl->status == MENU_USR2){ key = KEY_USR2; }else if (ptl->status == MENU_USR3){ key = KEY_USR3; }else if (ptl->status == MENU_CUT){ key = KEY_CUT; } pt += sprintf (pt,"%s,%s/",key,ptl->key.get()); } *pt = '\0'; } /* Format a context to a sub-meny entry */ static void html_setcontext_level(char *path, int upto) { char *pt = path; LEVEL_INFO *ptl = tblevel; for (int i=0; ikey.get()); } *pt = '\0'; } /* Format a path to a sub-meny entry */ static void html_setpath(char *path) { html_setpath_level (path,level); } static SSTRING targethost; // Target name + port use to reach us. /* Set a url pointing to a sub-menu entry */ void html_setaref ( const char *key, // Key for the url (path indeed) const char *text) // Highlit text { #if 1 char path[PATH_MAX]; html_setpath (path); html_printf ("%s" ,html_key,path,key,text); #else html_printf ("%s",key,text); #endif } static char tohex (int val) { if (val < 10){ val += '0'; }else{ val += ('A' - 10); } return val; } /* Format a message suitable as a path component of a URL. Space are transformed to ==. */ void html_formatkey (char *key, const char *ctl, ...) { va_list list; va_start (list,ctl); char buf[1000]; vsnprintf (buf,sizeof(buf)-1,ctl,list); va_end (list); char *pt = buf; while (*pt != '\0'){ char carac = *pt++; if (carac == ' ' || carac == '\n' || carac == '/' || carac == '>' || carac == '\t' || carac == '+'){ *key++ = '='; *key++ = '='; }else if (carac >= 128){ // Encode the character so browsers stop mangling them *key++ = '='; *key++ = tohex (carac >> 4); *key++ = tohex (carac & 0xf); }else{ *key++ = carac; } } *key = '\0'; } /* Send the header of the html document. */ void html_sendintro( const char *content_type, int length, // Length or -1 int expires, // How much seconds this document is expected to // be valid bool nocache) { time_t tim = time(NULL); html_printf ("HTTP/1.0 200 Document follows\r\n"); html_printf ("MIME-Version: 1.0\r\n"); extern char *revision; html_printf ("Server: linuxconf/%s\r\n",revision); char buf[200]; strcpy (buf,asctime(gmtime(&tim))); strip_end (buf); html_printf ("Date: %s\r\n",buf); html_printf ("Content-Type: %s\r\n",content_type); if (length != -1){ html_printf ("Content-Length: %d\r\n",length); } if (nocache){ html_printf ("Cache-Control: no-cache\r\n"); }else{ tim += expires; char bufexp[200]; strcpy (bufexp,asctime(gmtime(&tim))); strip_end (bufexp); html_printf ("Expires: %s\r\n",bufexp); html_printf ("Last-Modified: %s\r\n",buf); } html_printf ("\r\n"); } static CMDSOCK *cmd = NULL; /* Indicate that the html page has been sent and the connection can be closed. */ EXPORT void html_setdone() { html_drawdone = 1; html_flush (); if (cmd != NULL) cmd->closecli (html_cli); } /* HTML aware parts of linuxconf are allowed to override the path by cutting it and placing a dummy menu selection. Return the "cut" information recorded in a previous "run". Record at the same time the cut level which will be used by setcutinfo. */ EXPORT const char *html_getcutinfo () { /* #Specification: html mode / cut Complex dialog sequences in "HTML mode" work based on one important assumption: A sequence is made of menu and dialog collecting data and only the last dialog really commits the information. To understand this statement, one must understand the principle behind html support in linuxconf. The html mode is really transparent to application code in linuxconf. Because of this I guess it is not obvious: Linuxconf use a classical programming model and html is a stateless model. Classical programming (or modal) means that linuxconf works like this # -it draws a menu -it wait for a selection -it calls a sub-routine to handle this selection from the menu handling function. -it draws a sub-menu -it wait for a selection -and so on. # This means that linuxconf is always waiting for a specific input at a specific place in the code. Unfortunatly, while this programming model is by far the simplest, it does not work with html. With html we simply have no clue if the client will ever do anything with the dialog we sent. The principle of the html mode is that linuxconf is always walking from its main function to a given dialog. It draws the dialog (generates HTML) and escapes its way to the main (all intermediate dialog generate MENU_ESCAPE). The dialog drawn encode an action URL allowing linuxconf to rewalk the same path he did to draw the dialog. This time, instead of drawing, it accept the input and proceed further, generating a new dialog (with an associated action URL made of a longer path). Then linuxconf escape the its main. So while the user is visiting various dialog, the action URL grows and grows. It contains the path to rewalk all intermediate dialog up to the "target" dialog and so on. Now, what happen if along the path, one dialog accept the information and update some database. By doing so, the surrounding context may be changed in such a way that the action URL is not adequate to rewalk the path up to the target dialog. Here is an example # -You visit the user account menu -you select the list of normal user accounts -You "add" a new one -You fill the dialog and accept. -Linuxconf commit the new account to disk and calls the dialog to set the password. # Now there is a problem. We can't "walk" this path twice. The first time we create the new account, the next time, we can't succeed: The account is already created. The solution to this problem is to avoid commiting anything to disk until the last dialog. Linuxconf has used this for account creation for a long time, waiting for the new password before creating the account in a single step. But there are some cases where it is not possible to do so. For example, linuxconf with PAM support must commit the account to disk and then call PAM to change the password (PAM only operate on existing accounts). The solution in this case (when we can't postpone the commit) is to use the "cut" system. Mostly, there are two players. One set the cut level by doing. html_getcutinfo() This function either return a string or NULL. If it return NULL then the caller proceed normally. If it returns a string, then the caller process this string and select a different action, which is normally to skip one dialog and proceed to another one generally, the one right after the commit. Then further down the path, the dialog doing the commit will register the special string using html_setcutinfo() and will also call an another dialog. This trickery is building a new URL path which will go up to the cutting point, supply the proper MENU_CUT value and the associated info. */ const char *ret = NULL; cut_level = level; #if 0 fprintf (stderr,"cur level = %d\n",level); for (int i=0; i<=target_level; i++){ fprintf (stderr,"level %d status %d key :%s:\n",i,tblevel[i].status,tblevel[i].key.get()); } #endif if (level < target_level && tblevel[level].status == MENU_CUT){ ret = tblevel[level].key.get(); level++; } return ret; } /* Record the information which will be retrieve by the application in the next "run". html_getcutinfo() must have been called before this function, generally 1 or more dialogs before. */ EXPORT void html_setcutinfo (const char *s) { assert (cut_level != -1); tblevel[cut_level].key.setfrom (s); tblevel[cut_level].status = MENU_CUT; cut_info_set = true; } /* Signal that an URL is invalid */ void html_ivldurl() { html_printf ("500 Invalid URL\r\n"); html_setdone(); } /* Is called when a password is needed. The proper information is sent to the www browser requesting such a pawwword. The web browser will retransmit this password for the rest of the session. */ EXPORT void html_needpasswd() { html_printf ("HTTP/1.0 401 Unauthorized\r\n"); time_t tim = time(NULL); char buf[200]; strcpy (buf,asctime(gmtime(&tim))); strip_end (buf); html_printf ("Date: %s\r\n",buf); extern char *revision; html_printf ("Server: linuxconf/%s\r\n",revision); html_printf ("WWW-Authenticate: Basic realm=\"root/admin\"\r\n"); html_printf ("Content-type: text/html\r\n"); html_printf ("\r\n" "Authorization Required\r\n" "

Authorization Required

\r\n" "This server could not verify that you\r\n" "are authorized to access the document you\r\n" "requested. Either you supplied the wrong\r\n" "credentials (e.g., bad password), or your\r\n" "browser doesn't understand how to supply\r\n" "the credentials required.

\r\n" "\r\n" ); html_setdone(); } static void html_draw_history() { html_printf ("\n"); for (int i=0; i" "%s\n\n" ,i&1 ? "orange" : "orange" ,html_key ,path ,tblevel[i].title.get()); } html_printf ("
\n"); if (history_level > 0){ html_printf ("
\n"); } } static const char K_HTML[]="html"; static const char K_BODYPARM[]="bodyparm"; static const char K_HEADPARM[]="headparm"; /* Return the configurable parameters for the html BODY and HEAD directives */ void html_getpageparm(SSTRING &body, SSTRING &head) { body.setfrom (linuxconf_getval (K_HTML,K_BODYPARM,"")); head.setfrom (linuxconf_getval (K_HTML,K_HEADPARM,"")); } /* Save the configurable parameter for the HTML BODY and HEAD directives. The application must call linuxconf_save() after that. */ void html_setpageparm(const char *body, const char *head) { linuxconf_setcursys (subsys_stationid); if (body == NULL || body[0] == '\0'){ linuxconf_removeall (K_HTML,K_BODYPARM); }else{ linuxconf_replace (K_HTML,K_BODYPARM,body); } if (head == NULL || head[0] == '\0'){ linuxconf_removeall (K_HTML,K_HEADPARM); }else{ linuxconf_replace (K_HTML,K_HEADPARM,head); } } PRIVATE void DIALOG::html_draw_top() { /* #Specification: html mode / strategy / back on our feet Maybe this is an already visited dialog. This has been visited along the path followed while running through tblevel[]. While doing so, we have stamped dialog title in each level (in DIALOG::edithtml()). We will search now to see if we can find this dialog title in tblevel and correct target_level accordingly. This situation happen with the following case: # -The user is visiting one menu -he selects one dialog -he fills some fields and click on the "accept" button. -A subdialog is poped -The user fills it -After some sub-sub-dialogs the job is done and we are back to the original menu. # All these steps have collected a longer and longer html path. Each level of the path have captured the variable states used to go the next. So we have a very long path, but we have to fall on our feet again with a short path. */ for (int i=0; ititle) ==0){ target_level = i; level = i; break; } } html_sendintro ("text/html",-1,5,true); if (internal->html_bypass.top.is_empty()){ SSTRING body,head; html_getpageparm(body,head); html_printf ( "\n" "%s\n" "%s:%s\n" "\n" ,head.get(),html_host,internal->title.get()); if (!internal->internal_title.is_empty()){ html_printf ("

%s

\n" ,internal->internal_title.get()); } if (body.is_empty()) body.setfrom ("bgcolor=white"); html_printf ("\n",body.get()); // THis title was taking too much space //html_printf ("

%s

\n",internal->title.get()); //html_printf ("

\n


\n

\n"); html_draw_history(); }else{ html_write (internal->html_bypass.top.get() ,internal->html_bypass.top.getlen()); } } PRIVATE void DIALOG::html_draw_intro() { if (internal->icon.is_filled()){ html_printf ("\n"); html_printf ("
\n"); html_printf ("\n" ,internal->icon.get()); html_printf (""); } if (internal->html_bypass.intro.is_filled()){ html_write (internal->html_bypass.intro.get() ,internal->html_bypass.intro.getlen()); }else if (internal->intro.is_filled()){ html_printf ("
%s
\n" ,internal->intro.get()); } if (internal->icon.is_filled()){ html_printf ("
\n"); } } PRIVATE void DIALOG::html_draw_fields(int nof) { int lastf = getnb(); for (int i=0; ihtml_draw (i); if (lastf > 1 && nof == i && !f->is_readonly()){ html_printf (" \n"); } } } PRIVATE void DIALOG::html_draw_form(int nof) { char path[300]; html_setpath_level (path,target_level); html_printf ("

\n",html_key,path); if (!internal->html_bypass.body.is_empty()){ html_write (internal->html_bypass.body.get() ,internal->html_bypass.body.getlen()); html_printf ("\n"); }else{ html_printf ("
\n"); html_printf ("\n"); if (curvars != NULL){ // Define variable for previous level int n = curvars->getnb(); for (int i=0; igetvar(i); if (isdigit(name[0]) && atoi(name)\n" ,name,curvars->getval(i)); } } } html_draw_fields(nof); html_printf ("
\n"); html_printf ("
\n"); } html_printf ("

\n"); internal->buttons->html_draw (); html_printf ("

\n"); } PRIVATE void DIALOG::html_draw_end() { html_write (internal->html_bypass.end.get() ,internal->html_bypass.end.getlen()); html_printf ("\n"); html_printf ("\n\n"); } /* Draw the complete dialog including a subdialog (error, password request) */ PRIVATE void DIALOG::html_draw (DIALOG *spc, int nof) { html_draw_top(); if (spc != NULL){ spc->html_draw_intro(); spc->html_draw_fields(-1); html_printf ("
\n"); } html_draw_intro(); html_draw_form(nof); html_draw_end(); } /* Draw the complete dialog */ PRIVATE void DIALOG::html_draw (int nof) { html_draw(NULL,nof); } /* Load all field of the dialog with the received value and check that everything is valid Return -1 if any error. */ PRIVATE int DIALOG::html_validate (SSTRING &msg) { int ret = 0; int lastf = getnb(); for (int i=0; iis_readonly()){ int ok = f->html_validate (i); if (ok != 0){ if (msg.is_empty()){ msg.setfromf (MSG_U(F_DIALOG,"Dialog %s\r\n

\r\n") ,internal->title.get()); } msg.appendf (MSG_U(F_FIELDSTATE ,"Field number %d (%s) out of sync
\n") ,i+1,f->prompt); } ret |= ok; } } return ret; } static void html_encodespaces(char *dst, const char *src) { while (*src != '\0'){ char car = *src++; if (car == ' ' || car >= 128){ *dst++ = '='; *dst++ = tohex (car >> 4); *dst++ = tohex (car & 0xf); }else{ *dst++ = car; } } *dst = '\0'; } PUBLIC void BUTTONS_INFO::html_draw() { for (int i=0; i" "\"%s\"\n" ,helpfile.get() ,tb_icon[i] ,tb_title[i]); }else{ if (code == MENU_CANCEL || code == MENU_QUIT || code == MENU_OK){ continue; // Those buttons are useless in html mode }else if (code == MENU_ACCEPT){ name = KEY_ACCEPT; }else if (code == MENU_ADD){ name = KEY_ADD; }else if (code == MENU_SAVE){ name = "save"; }else if (code == MENU_DEL){ name = KEY_DEL; }else if (code == MENU_INS){ name = KEY_INS; }else if (code == MENU_EDIT){ name = KEY_EDIT; }else if (code == MENU_YES){ name = KEY_YES; }else if (code == MENU_NO){ name = KEY_NO; }else if (code == MENU_MORE){ name = KEY_MORE; }else if (code == MENU_USR1){ name = KEY_USR1; }else if (code == MENU_USR2){ name = KEY_USR2; }else if (code == MENU_USR3){ name = KEY_USR3; }else if (code == MENU_CUT){ name = KEY_CUT; }else{ fprintf (stderr,"old button\n"); } char key[PATH_MAX]; html_encodespaces (key,tb_icon[i]); html_printf ("\n" ,name,key,tb_title[i]); } } } void html_forgetdialog (DIALOG *) { } void html_adddialog (DIALOG *) { } static SSTRINGS html_basepaths; static void html_initbasepaths() { if (html_basepaths.getnb()==0) html_basepaths.add (new SSTRING (USR_LIB_LINUXCONF)); } /* Register a basepath for html file, PNGs and icons used by modules. */ void html_registerpath (const char *basepath) { /* #Specification: html / modules / base path Linuxconf modules may provide their own help file and icons in their own directory hierarchy. Linuxconf search in /usr/lib/linuxconf and then in the module's supplied path. */ html_initbasepaths(); html_basepaths.add (new SSTRING (basepath)); } /* Find one file in the different hierarchy available to linuxconf and its modules */ int html_locatefile ( const char *fname, const char *extension, char *path, int maxlen) { int ret = -1; if (strstr(fname,"..")==NULL){ html_initbasepaths(); path[0] = '\0'; for (int i=0; iget(),fname ,extension); if (file_exist (path)){ ret = 0; break; } } } return ret; } static void html_copy (const char *fname, int dointro) { char path[PATH_MAX]; if (html_locatefile(fname,"",path,PATH_MAX)==-1){ const char *ptpng = strstr(fname,".png"); if (strncmp(fname,"images/",7)==0 && ptpng!=NULL){ /* #Specification: HTML / buttons / generating PNGs When a png image is missing for a button, it is generated using the GD library. The button file name is used as the text. */ if (dointro){ html_sendintro("image/png",-1,24*60*60,false); } html_flush(); int len = (int)(ptpng - fname) - 7; if (len < PATH_MAX-1){ memmove (path,fname+7,len); path[len] = '\0'; FILE *fout = fdopen (html_cli,"w"); button_text2png (path,fout); fclose (fout); } }else{ html_printf ("500 file %s not found\r\n",fname); html_flush(); } }else{ FILE *fin = fopen (path,"r"); if (fin == NULL){ html_printf ("500 can't open file %s\r\n",path); html_flush(); }else{ if (debug) fprintf (stderr,"Sending :%s:\n",path); struct stat st; int size = -1; if (stat(path,&st)!=-1) size = st.st_size; if (dointro){ html_sendintro(strstr(fname,".png")!=NULL ? "image/png" : "text/html" ,size,24*60*60,false); } html_flush(); char buf[3*4096]; int n; while ((n=fread(buf,1,sizeof(buf)-1,fin))> 0){ //if (debug) fwrite (buf,1,n,stderr); buf[n] = '\0'; char *pt = strstr(buf,"$(HOSTNAME)"); if (pt == NULL){ write (html_cli,buf,n); }else{ if (pt > buf) write(html_cli,buf,(int)(pt-buf)); // Plug the hostname write (html_cli,html_host,strlen(html_host)); pt += 11; int len = n - (int)(pt-buf); if (len > 0){ write (html_cli,pt,len); } } } html_printf ("\r\n"); html_flush(); fclose (fin); } } } /* #Specification: html mode / general strategy Here is a basic explanation of the way linuxconf manage html page while not being that much http/html aware. Some fact: # -Linuxconf is a classical modal program. Mostly, only one dialog has the focus at a time. Further, in linuxconf, there is generally only one dialog at once. This is acceptable for admin tasks anyway. -html mode work with the concept of hope. You send a page to a user and he may click on a button one day or never. You better not wait for it. So good for a modal program. # The strategy is simple. Linuxconf is always waiting at the top level of the menu hierachy for an html request. Each request contain a path (using /'s) allowing linuxconf to navigate in the menu hisrarchy up to a certain "level". At this point linuxconf simply draw the dialog or menu and ... get back to the top level. This behavior of always getting back is trigger by returning MENU_ESCAPE, so each part of linuxconf must be "ESCAPE" aware, which is good anyway. So when we get a path, we parse it and store it in a table and note the target_level. Important assumption here: All dialog with input are preceded in the hierarchy by menus. This make sens anyway. This disable support for popup dialog however. So we parse and store the path and lauch linuxconf from the top level menu. While navigating in its code, linuxconf draw menus (not really) and wait for input (not really also). If this menu is not of the proper level, the path information will be used as a key to identify which menu item was choosen. A MENU_OK is then returned and linuxconf continue to navigate further into sub-sub-menus (In fact the exact button returned is contain in the path). At some point, it crosses the target level. At the target level, there is two cases: Either this is GET or a POST. If this is a GET, we draw the dialog and escape away to the main loop. If this is a POST, then we load all the fields of the dialog with the values received from the POST. Based on some special values, we know which buttons was hit and return appropriatly MENU_ACCEPT, MENU_ADD and so on to the application. Three things may happen. Either there is some error message (The application identify those with html_setpopup()) or linuxconf is happy and exited to the previous level menu (Leaving this dialog screen) or linuxconf provide a sub-dialog asking for more info. Most of the logic here is controlled by the DIALOG::edithtml() function. */ /* Record that the next DIALOG object is a popup dialog (error message) */ void html_setpopup() { html_popup = true; } /* Forget about html_setpopup() */ void html_resetpopup() { html_popup = false; } PRIVATE MENU_STATUS DIALOG::edithtml(int &nof) { MENU_STATUS ret = MENU_ESCAPE; if (!html_drawdone){ static SSTRING popup_str; if (html_popup){ // Error messages (popups) are folded into the underlying dialog // so do not count as a level. // Multiple popups may be concatenated html_draw_intro(); html_draw_fields(-1); popup_str.append (html_buf); html_len = 0; html_popup = false; }else{ if (level == target_level){ static MENU_STATUS was_button; // Was it a POST with the // button accept if (curvars != NULL && !html_postdone){ SSTRING msg; if (html_validate(msg) != -1){ // Those names need not be translated // see BUTTONS_INFO::html_draw() if (html_butexist("ok.x")){ ret = MENU_OK; }else if (html_butexist("accept.x")){ ret = MENU_ACCEPT; }else if (html_butexist("add.x")){ ret = MENU_ADD; }else if (html_butexist("edit.x")){ ret = MENU_EDIT; }else if (html_butexist("del.x")){ ret = MENU_DEL; }else if (html_butexist("save.x")){ ret = MENU_SAVE; }else if (html_butexist("yes.x")){ ret = MENU_YES; }else if (html_butexist("no.x")){ ret = MENU_NO; }else if (html_butexist("more.x")){ ret = MENU_MORE; }else if (html_butexist("usr1.x")){ ret = MENU_USR1; }else if (html_butexist("usr2.x")){ ret = MENU_USR2; }else if (html_butexist("usr3.x")){ ret = MENU_USR3; }else{ if (debug) fprintf (stderr,"Invalid button\n"); ret = MENU_ACCEPT; } was_button = ret; html_postdone = 1; }else{ html_draw_top(); html_printf ("500 %s

\r\n" ,MSG_U(E_MISMATCH,"dialog state mismatch")); html_printf ("


%s\n",msg.get()); html_setdone(); } }else{ if (curvars != NULL){ if (cut_level != target_level || !cut_info_set){ tblevel[level].status = was_button; tblevel[level].key.setfrom (curvars->getid()); } target_level++; level++; html_draw_top(); if (!popup_str.is_empty()){ popup_str.copy (html_buf + html_len); html_len = strlen(html_buf); }else{ // When there is an error message // we avoid displaying too much // This is why the intro is optionnal. html_draw_intro(); } html_draw_form(nof); html_draw_end(); html_setdone(); }else{ html_draw(nof); html_setdone(); } } }else if (level < target_level){ LEVEL_INFO *pt = tblevel + level; pt->title.setfrom (internal->title); int n = getnb(); nof = -1; for (int i=0; iformat_htmlkey(key,i); if (pt->key.cmp(key)==0){ nof = i; break; } } if (nof == -1 && pt->status == MENU_OK){ history_level = level; html_draw_top(); char path[PATH_MAX]; help_notfound.getrpath(path); strcat (path,".html"); html_copy (path,0); html_printf ("\n\n"); html_flush(); html_setdone(); }else{ ret = pt->status; other_vars = varval_get(pt->key.getval()); //printf ("Intermediate validate level %d %d\n",level,ret); SSTRING msg; if (html_validate(msg) == -1){ html_draw_top(); html_printf ("501 %s

\r\n",MSG_R(E_MISMATCH)); html_printf ("


%s\n",msg.get()); html_setdone(); } //printf ("Intermediate validate level %d end\n",level); } level++; } popup_str.setfrom (""); } } return ret; } /* Split the PATH in tblevel[] array. Return -1 if any errors */ static int html_parsepath(char *pt) { int ret = 0; if (debug) fprintf (stderr,"Parse path :%s:\n",pt); int len = strlen(pt); if (len > 0 && pt[len-1] == '/') pt[len-1] = '\0'; history_level = level = target_level = 0; cut_level = -1; cut_info_set = false; if (pt[0] == '/') pt++; while (*pt != '\0'){ char *split = strchr (pt,'/'); if (split != NULL) *split++ = '\0'; char *comma = strchr(pt,','); if (comma != NULL){ *comma++ = '\0'; if (comma[0] != '\0') history_level++; } if (target_level == MAX_WWW_LEVEL){ ret = -1; break; }else{ LEVEL_INFO *ptl = &tblevel[target_level++]; ptl->key.setfrom (comma); if (strcmp(pt,KEY_OK)==0){ ptl->status = MENU_OK; }else if (strcmp(pt,KEY_ACCEPT)==0){ ptl->status = MENU_ACCEPT; }else if (strcmp(pt,KEY_ADD)==0){ ptl->status = MENU_ADD; }else if (strcmp(pt,KEY_DEL)==0){ ptl->status = MENU_DEL; }else if (strcmp(pt,KEY_YES)==0){ ptl->status = MENU_YES; }else if (strcmp(pt,KEY_NO)==0){ ptl->status = MENU_NO; }else if (strcmp(pt,KEY_EDIT)==0){ ptl->status = MENU_EDIT; }else if (strcmp(pt,KEY_MORE)==0){ ptl->status = MENU_MORE; }else if (strcmp(pt,KEY_USR1)==0){ ptl->status = MENU_USR1; }else if (strcmp(pt,KEY_USR2)==0){ ptl->status = MENU_USR2; }else if (strcmp(pt,KEY_USR3)==0){ ptl->status = MENU_USR3; }else if (strcmp(pt,KEY_CUT)==0){ ptl->status = MENU_CUT; } if (split != NULL){ pt = split; }else{ break; } } } return ret; } static void html_dbglog (const char *title, const char *str) { static const char LNXHTML_DBG[]="/var/run/lnxhtml.dbg"; static char dbg_on = 0; if (dbg_on == 0){ dbg_on = 1; if (getenv("DBG_LNXHTML")!=NULL) dbg_on = 2; } if (dbg_on == 2){ int old = umask (0077); FILE *f = fopen (LNXHTML_DBG,"a"); umask (old); if (f != NULL){ fprintf (f,"======%s=======\n",title); fputs (str,f); fclose (f); } }else{ // This file contain sensible information, better delete it // as soon as the debug is done unlink (LNXHTML_DBG); } } static char hextoi (char asc) { return isdigit(asc) ? asc - '0' : (toupper(asc) - 'A') + 10; } /* Extract and decode one line */ static const char *html_decode (const char *str, char *buf, int sizbuf) { char *pt = buf; char *endbuf = buf + sizbuf-1; while (*str != '\0' && *str != '\n' && pt < endbuf){ if (*str == '%'){ str++; *pt++ = hextoi(*str++) * 16 + hextoi(*str++); }else if (*str == '+'){ *pt++ = ' '; str++; }else{ *pt++ = *str++; } } *pt = '\0'; strip_end (buf); { // Check if there was some   placed by the encoding earlier // This is done after the strip_end. pt = buf; while (*pt != '\0'){ if (*pt == 0xa0) *pt = ' '; pt++; } } if (*str == '\n') str++; return str; } static void html_parsevar (const char *buf) { { char context[200]; html_setcontext_level(context,target_level); curvars = new HTML_VARVAL(context); } while (1){ char *pt = strchr(buf,'='); if (pt == NULL){ break; }else{ *pt++ = '\0'; char *end = strchr(pt,'&'); if (end != NULL){ *end++ = '\0'; } char var[200]; char val[2000]; html_decode (buf,var,sizeof(var)); html_decode (pt,val,sizeof(val)); // Fix for Mac browser which are sending \r at the end of fields int last = strlen(val)-1; if (last >= 0 && val[last] == '\r') val[last] = '\0'; curvars->add (var,val); if (end == NULL) break; buf = end; } } } static SSTRING htmlbody; // Reminder of a request, kept for other // protocol started from the http port // such as remadmin /* Return the body of the last http request (The lines following the first empty one) */ const char *http_getbody() { return htmlbody.get(); } /* Decode spaces (and only spaces) encoded as =20 */ static void html_decodespaces (char *dst, const char *src, int maxlen) { while (*src != '\0' && maxlen > 0){ if (src[0] == '=' && src[1] == '2' && src[2] == '0'){ src += 3; *dst++ = ' '; }else{ *dst++ = *src++; } maxlen--; } *dst = '\0'; } /* Parse a potentially completed header. Extract the "get" command or "post" command. Return -1 if any error. Return 0 if the header was not completed Return 1 if the header was completed */ static int html_parse ( const char *str, char file_request[PATH_MAX], // Will contain a file to transmit to the client char username[50], // Will hold the username char password[50], // and password provided by the browser HELP_FILE &intro, char module_key[50], bool &remadmin) // Will become true if remote administration // is required { if (debug) fprintf (stderr,"Parse header: %s\n",str); unsigned expected_length = 0; int html_post = 0; int ret = 0; int get_ok = 0; file_request[0] = '\0'; remadmin = false; html_submit = MENU_NULL; username[0] = '\0'; password[0] = '\0'; module_key[0] = '\0'; history_level = level = target_level = 0; targethost.setfrom (""); while (*str != '\0' && ret != -1){ char buf[httpmaxsize]; str = html_decode (str,buf,sizeof(buf)); if (buf[0] == '\0'){ if (get_ok){ html_reset(); htmlbody.setfrom (str); if (html_post){ char t[100]; sprintf (t,"expe %u, got %u\n",expected_length,strlen(str)); html_dbglog ("detail",t); if (strlen(str) >= expected_length){ strcpy_cut (buf,str,sizeof(buf)); html_parsevar (buf); ret = 1; } break; }else{ ret = 1; } }else{ ret = -1; } }else{ char cmd[200]; char *pt = str_copyword (cmd,buf,sizeof(cmd)-1); strupr (cmd); int is_get = strcmp(cmd,"GET")==0; int is_post = strcmp(cmd,"POST")==0; if (is_get || is_post){ pt = str_skip (pt); char path[1000]; char parm[1000]; parm[0] = '\0'; str_copyword (path,pt,sizeof(path)); char *ptparm = strchr(path,'?'); if (ptparm != NULL){ *ptparm++ = '\0'; strcpy_cut (parm,ptparm,sizeof(parm)); } if (strstr(path,"..")!=NULL){ ret = -1; break; }else if (strncmp(path,"/help:",6)==0){ str_copyword (file_request,path+6,PATH_MAX-1); }else if (strncmp(path,"/images:",8)==0){ html_decodespaces (file_request,path+8,PATH_MAX-1); // str_copyword (file_request,path+8,PATH_MAX-1); }else if (strncmp(path,"/remadm:",8)==0){ remadmin = true; }else if (strncmp(path,"/html:",6)==0){ strcpy (html_key,"/html:"); if (html_parsepath(path+6)==-1) ret = -1; }else if (strncmp(path,"/htmlmod:",9)==0){ char *modkey = path + 9; char *pt = strchr(modkey,':'); if (pt != NULL){ *pt++ = '\0'; if (strlen (modkey)<40){ if (html_parsepath(pt)==-1) ret = -1; strcpy (module_key,modkey); snprintf (html_key,sizeof(html_key)-1,"/htmlmod:%s:",modkey); } } }else{ intro.getrpath(file_request); strcat (file_request,".html"); } html_post = is_post; get_ok = 1; }else if (stricmp(cmd,"Content-length:")==0){ expected_length = atoi(pt); }else if (stricmp(cmd,"Host:")==0){ targethost.setfromf ("//%s",str_skip(pt)); }else if (stricmp(cmd,"Authorization:")==0){ char basic[1000]; pt = str_copyword (basic,pt,sizeof(basic)-1); if (stricmp(basic,"Basic")==0){ pt = str_copyword (basic,pt,sizeof(basic)); char pw[1000]; if (base64_decode(pw,sizeof(pw),basic) != -1){ char *ptpt = strchr(pw,':'); if (ptpt != NULL){ *ptpt++ = '\0'; strncpy (username,pw,50-1); strncpy (password,ptpt,50-1); username[49] = '\0'; password[49] = '\0'; } } } } } } return ret; } /* #Specification: html mode / multi user A single process is handling all requests, one at a time. At most 200 simultaneous http request may occur. The exceeding ones are silently dropped! */ static const int MAX_WWW_CLIENTS=200; static SSTRING *tbs[MAX_WWW_CLIENTS]; /* Get a command (A "get" indeed) from a client (Web browser). parse this command into a path that will silently show the way so linuxconf will silently travel to the proper menu, draw it and quit. Return -1 if there was some error or nothing has happen for a long time (no more job). Nothing to do for Linuxconf. */ int html_get ( int _debug, HELP_FILE &intro, int timeout, // in minutes char module_key[50],// Special key allowing a URL to point straight // into a module menuing int &remhandle) // Will contain the handle for the remote // GUI mode or -1 { remhandle = -1; debug = _debug; if (cmd == NULL) cmd = new CMDSOCK (debug ? port : -1,1); int ret = -1; while (1){ int ok = cmd->listen(timeout*60); if (ok <= 0){ if (errno != EINTR) break; }else{ char buf[5000]; int nb; int cli; bool is_timeout; if ((nb=cmd->readnext (buf,sizeof(buf)-1,cli,is_timeout))>=0){ if (nb == 0){ cmd->closecli (cli); }else if (cli >= MAX_WWW_CLIENTS){ cmd->closecli (cli); }else if (tbs[cli] == NULL && html_access_check(cli)!=0){ html_cli = cli; html_printf ("500 access denied: Check config/networking/misc/linuxconf network access\r\n"); html_printf ("

\r\n"); html_printf ("By default, linuxconf network access is disabled
\r\n"); html_printf ("Access is generally granted for few locations only
\r\n"); html_flush(); cmd->closecli (cli); }else{ buf[nb] = '\0'; if (tbs[cli] == NULL){ tbs[cli] = new SSTRING; } tbs[cli]->append (buf); if (tbs[cli]->getlen()>httpmaxsize){ /* #Specification: html mode / input overflow If linuxconf receive an http request exceding 20000 bytes, it is silently flushed. */ html_access_log(cli,MSG_U(E_IVLDHTMLREQ,"Invalid html request, too long")); cmd->closecli(cli); delete tbs[cli]; tbs[cli] = NULL; }else{ char file_request[PATH_MAX]; char username[50]; char password[50]; html_dbglog ("so far",tbs[cli]->get()); bool remadmin = false; int ok = html_parse (tbs[cli]->get(),file_request ,username,password,intro,module_key ,remadmin); perm_setaccess(username,password); if (ok == -1){ html_access_log(cli,MSG_R(E_IVLDHTMLREQ)); cmd->closecli (cli); delete tbs[cli]; tbs[cli] = NULL; }else if (ok > 0){ html_cli = cli; delete tbs[cli]; tbs[cli] = NULL; if (file_request[0] != '\0'){ char mbuf[1000+PATH_MAX]; snprintf (mbuf,sizeof(mbuf),MSG_U(L_FREQUEST ,"File request: %s") ,file_request); html_access_log(cli,mbuf); html_copy (file_request,1); cmd->closecli (cli); }else if (remadmin){ remhandle = cli; cmd->forgetcli(cli); ret = 0; break; }else{ char mbuf[1000],pathreq[1000]; html_setpath_level (pathreq,target_level); sprintf (mbuf,MSG_U(L_REQUEST ,"Request: %s") ,pathreq); html_access_log(cli,mbuf); ret = 0; break; } } } } } } } return ret; } /* Try to identify under which name we have been contacted. This is mainly used by the userconf/upass.c which provide POP users a way to change their password using an html form. upass.c support virtual email domain and determine the virtual domain using the output of the function. It return -1 if it can't identify our own name */ int html_getourname(char *name) { int ret = -1; struct sockaddr_in adr; unsigned int len = sizeof(adr); if (getsockname (html_cli,(struct sockaddr*)&adr,&len) != -1){ struct hostent *ent; /* syslog (LOG_ERR,"connexion de %d %x",adr.sin_port,adr.sin_addr); */ ent = gethostbyaddr ((const char*)&adr.sin_addr ,sizeof(adr.sin_addr.s_addr),AF_INET); /* syslog (LOG_DEBUG,"connexion de %p",ent); */ if (ent != NULL){ strcpy (name,ent->h_name); /* syslog (LOG_DEBUG,"connexion de %s",name); */ ret = 0; }else{ long a = ntohl (*(long*)(&adr.sin_addr)); syslog (LOG_ERR,"Can't convert IP number %lu.%lu.%lu.%lu to name, using main domain" ,(a>>24)&0xff,(a>>16)&0xff,(a>>8)&0xff,a&0xff); } }else{ syslog (LOG_ERR,"getsockname failed (errno %m)"); } return ret; }