1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <ctype.h>
4 #include <string.h>
5 
6 #include "lsg2.h"
7 
8 const char hextable[] = "0123456789ABCDEF";
9 
decode_pathinfo(const char * pathinfo)10 int decode_pathinfo(const char *pathinfo)
11 {
12    char *ptr;
13    char *buffer;
14 
15    buffer = strdup(pathinfo);
16 
17    ptr = buffer;
18 
19    ptr = strtok(ptr,"/");
20 
21    while (ptr ? *ptr : 0) {
22       char *ptr2;
23       char *ptr2lower;
24 
25       ptr2 = strchr(ptr,'=');
26       if (ptr2) {
27          *ptr2++ = 0;
28 
29          ptr2lower = LMAPI->lowerstr(ptr2);
30 
31          LMAPI->log_printf(18,"Parsing pathinfo: %s (%s)\n", ptr, ptr2);
32 
33          /* Take our pathinfo pieces */
34          switch (tolower(*ptr)) {
35             case 'l':
36               LMAPI->set_var("lcgi-list",ptr2lower,VAR_GLOBAL);
37               break;
38             case 'u':
39               LMAPI->set_var("lcgi-user",ptr2lower,VAR_GLOBAL);
40               break;
41             case 'c':
42               LMAPI->set_var("lcgi-cookie",ptr2,VAR_GLOBAL);
43               break;
44             case 'm':
45               LMAPI->set_var("lcgi-mode",ptr2,VAR_GLOBAL);
46               break;
47             case 'p':
48               LMAPI->set_var("lcgi-pass",ptr2,VAR_GLOBAL);
49               break;
50          }
51 
52          free(ptr2lower);
53 
54          *(ptr2 - 1) = '=';
55 
56       }
57       ptr = strtok(NULL,"/");
58    }
59    free(buffer);
60    return 1;
61 }
62 
63 /* Decode a key=value pair */
decode_param(const char * param)64 int decode_param(const char *param)
65 {
66    char *buffer;
67    char *paramname, *endvalue, *ptr, *ptr2;
68    int returnval;
69 
70    /* Get our working buffer... */
71    buffer = (char *)strdup(param);
72 
73    /* ...and split it into the key and encoded value */
74    ptr = (char *)strchr(buffer,'=');
75    *ptr = 0;
76    paramname = (char *)strdup(buffer);
77 
78    /* Set up the workspace for the value decoding */
79    endvalue = (char *)malloc(strlen(param));
80    memset(endvalue,0,strlen(param));
81 
82    ptr2 = endvalue;
83 
84    ptr++;
85 
86    /* Walk the encoded value, changing from CGI-encoded to
87       normal text. */
88    while (*ptr) {
89       if (*ptr == '+') {
90         *ptr2 = ' ';
91       } else if (*ptr == '%') {
92         char temp;
93         const char *temp2;
94         int value;
95 
96         /* Strange, ugly, scary hack to decode hexadecimal,
97            because sscanf was doing odd things. */
98         temp = toupper(*(ptr + 1));
99         temp2 = strchr(hextable, temp);
100         value = (temp2 - &hextable[0]) * 16;
101         temp = toupper(*(ptr + 2));
102         temp2 = strchr(hextable, temp);
103         value += temp2 - &hextable[0];
104 
105         ptr += 2;
106         *ptr2 = value;
107       } else *ptr2 = *ptr;
108 
109       ptr2++;
110       ptr++;
111    }
112 
113    *ptr2 = 0;
114 
115    if (strlen(endvalue)) {
116       char *endvaluelower;
117 
118       /* We can only set variables beginning in lcgi- this way,
119          for security reasons.
120 
121          HOWEVER, lcgipl- is also allowed and will be stored in a
122          temporary key/value structure, for the purposes of things
123          like setting flags on yourself and doing the list
124          configuration screen. */
125 
126       endvaluelower = LMAPI->lowerstr(endvalue);
127 
128       if (strncasecmp(paramname,"lcgi-",5) == 0) {
129         if ((strcasecmp(paramname,"lcgi-user") == 0) ||
130             (strcasecmp(paramname,"lcgi-list") == 0)) {
131            LMAPI->set_var(paramname,endvaluelower,VAR_GLOBAL);
132         } else {
133            LMAPI->set_var(paramname,endvalue,VAR_GLOBAL);
134         }
135 
136         returnval = 1;
137       } else {
138         if (strncasecmp(paramname,"lcgipl-",7) == 0) {
139            LMAPI->add_cgi_tempvar((paramname+7),endvalue);
140         }
141         returnval = 0;
142       }
143 
144      free(endvaluelower);
145    } else returnval = 0;
146 
147    /* It's always good to clean up after ourselves. */
148    free(paramname);
149    free(endvalue);
150    free(buffer);
151 
152    return(returnval);
153 }
154 
lsg2_internal_error_fallback(const char * errortext)155 void lsg2_internal_error_fallback(const char *errortext)
156 {
157    printf("<body bgcolor=#ffffff text=#000000>\n");
158    printf("<title>LSG/2 Internal Error: %s</title>\n", errortext);
159    printf("<font size=+3>LSG/2 Error: %s</font>\n", errortext);
160    printf("<HR>\n");
161    printf("An internal and unrecoverable error has occured.\n");
162    printf("Please contact <a href=\"mailto:%s\">%s</a> and report\n",
163      LMAPI->get_string("listserver-admin"),
164      LMAPI->get_string("listserver-admin"));
165    printf("how you got this error.");
166    printf("<HR>\n");
167 }
168 
lsg2_internal_error(const char * errortext)169 void lsg2_internal_error(const char *errortext)
170 {
171    LMAPI->set_var("lcgi-error", errortext, VAR_TEMP);
172    if (!LMAPI->cgi_unparse_template("error"))
173      lsg2_internal_error_fallback(errortext);
174 }
175 
MODE_HANDLER(mode_lsg2)176 MODE_HANDLER(mode_lsg2)
177 {
178    int len;
179    char *inbuffer, *ptr;
180    struct listserver_cgi_mode *curmode;
181    char *tbuf;
182 
183    /* turn off loose host matching */
184    LMAPI->set_var("no-loose-domain-match", "yes", VAR_GLOBAL);
185 
186    /* Generic header */
187 
188    if (LMAPI->get_bool("lsg2-iis-support"))
189       printf("HTTP/1.1 200 OK\n");
190 
191    printf("Content-type: text/html\n");
192    printf("LSG2-Version: %s\n", LSG2_VERSION);
193    printf("\n\n");
194 
195    /* Let's do a sanity check.. */
196    if (!LMAPI->get_var("lsg2-cgi-url") ||
197        !LMAPI->get_var("cgi-template-dir")) {
198      lsg2_internal_error("Incorrectly Configured");
199      return MODE_END;
200    }
201 
202    /* Get the content-length variable... */
203    ptr = getenv("CONTENT_LENGTH");
204 
205    /* ...making sure it's valid... */
206    if (ptr ? atoi(ptr) : 0) {
207       /* ...and read in the POST data. */
208       len = atoi(ptr);
209 
210       inbuffer = (char *)malloc(len + 2);
211       memset(inbuffer,0,len + 2);
212       fgets(inbuffer,len + 1,stdin);
213 
214       /* Walk the set of keys */
215       ptr = (char *)strtok(inbuffer,"&");
216 
217       while(ptr ? *ptr : 0) {
218          decode_param(ptr);
219          ptr = (char *)strtok(NULL,"&");
220       }
221 
222       /* ...and clean up after ourselves. */
223       free(inbuffer);
224    }
225 
226    /* Do we have a PATH_INFO variable? */
227    ptr = getenv("PATH_INFO");
228    if (ptr) {
229       LMAPI->set_var("tlcgi-pathinfo",ptr,VAR_GLOBAL);
230       decode_pathinfo(ptr);
231    }
232 
233    /* Now, any variables in the POST'd form that began with
234       'lcgi-' and which are valid Listar variables have been
235       set, so we can simply continue along.  */
236 
237    if (LMAPI->get_var("lcgi-mode")) {
238        curmode = LMAPI->find_cgi_mode(LMAPI->get_string("lcgi-mode"));
239        tbuf = strdup(LMAPI->get_string("lcgi-mode"));
240    } else {
241      if (LMAPI->get_var("lcgi-user") && (LMAPI->get_var("lcgi-pass") ||
242            LMAPI->get_var("lcgi-cookie"))) {
243         if (LMAPI->get_var("lcgi-list")) {
244            curmode = LMAPI->find_cgi_mode("listmenu");
245            tbuf = strdup("listmenu");
246         } else {
247            curmode = LMAPI->find_cgi_mode("login");
248            tbuf = strdup("login");
249         }
250      } else {
251         curmode = LMAPI->find_cgi_mode("default");
252         tbuf = strdup("default");
253      }
254    }
255 
256    if (LMAPI->get_var("lcgi-list")) {
257      LMAPI->set_context_list(LMAPI->get_string("lcgi-list"));
258    }
259 
260    LMAPI->set_var("lcgi-mode",tbuf,VAR_GLOBAL);
261 
262    LMAPI->set_var("lcgi-remote-host",getenv("REMOTE_ADDR"),VAR_GLOBAL);
263    LMAPI->set_var("lcgi-server-soft",getenv("SERVER_SOFTWARE"),VAR_GLOBAL);
264 
265    if (!curmode) {
266      lsg2_internal_error("Invalid Mode");
267      printf("<!-- Mode was '%s' -->\n", tbuf);
268      free(tbuf);
269      return MODE_END;
270    } else {
271      free(tbuf);
272      (curmode->mode)();
273      return MODE_END;
274    }
275 }
276 
CMDARG_HANDLER(cmdarg_lsg2)277 CMDARG_HANDLER(cmdarg_lsg2)
278 {
279    LMAPI->set_var("mode","lsg2",VAR_GLOBAL);
280    LMAPI->set_var("fakequeue","yes",VAR_GLOBAL);
281 
282    return CMDARG_OK;
283 }
284 
lsg2_update_cookie(char * buffer,int length)285 int lsg2_update_cookie(char *buffer, int length)
286 {
287    const char *oldcookie;
288    const char *curcookie;
289    char cookiefile[BIG_BUF];
290    char cookie[BIG_BUF];
291    char databuf[BIG_BUF];
292    time_t now;
293    int seconds;
294    const char *fromaddy = LMAPI->get_string("lcgi-user");
295    const char *remote = LMAPI->get_string("lcgi-remote-host");
296 
297    if (!LMAPI->get_var("lcgi-user")) {
298       lsg2_internal_error("Invalid form data, no user.");
299       return 0;
300    }
301 
302    LMAPI->buffer_printf(cookiefile, sizeof(cookiefile) - 1, "%s/SITEDATA/cookies",
303                         LMAPI->get_string("lists-root"));
304 
305    time(&now);
306    seconds = LMAPI->get_seconds("lsg2-cookie-duration");
307    now += seconds;
308 
309 #ifdef OBSDMOD
310     LMAPI->buffer_printf(databuf, sizeof(databuf) - 1, "%lX;%s;%s", (long)now, fromaddy, remote);
311 #else
312 #ifdef DEC_UNIX
313     LMAPI->buffer_printf(databuf, sizeof(databuf) - 1, "%X;%s;%s", now, fromaddy, remote);
314 #else
315     LMAPI->buffer_printf(databuf, sizeof(databuf) - 1, "%lX;%s;%s", (long)now, fromaddy, remote);
316 #endif /* DEC_UNIX */
317 #endif /* OBSDMOD */
318 
319    oldcookie = LMAPI->find_cookie(cookiefile,'W',
320                      LMAPI->get_string("lcgi-user"));
321    curcookie = LMAPI->get_var("lcgi-cookie");
322 
323    if ((!oldcookie && !curcookie && (strcasecmp(LMAPI->get_var("lcgi-mode"),"passwd") == 0)) ||
324        (curcookie && !LMAPI->match_cookie(curcookie,LMAPI->get_string("lcgi-user")))) {
325       char forgeWarning[BIG_BUF];
326 
327       lsg2_internal_error("Cookie may be forged?");
328 
329       LMAPI->set_var("task-form-subject","Possible attempted forgery!",
330                   VAR_TEMP);
331       LMAPI->task_heading(LMAPI->get_var("list-owner"));
332       LMAPI->buffer_printf(forgeWarning,BIG_BUF - 1,
333          "Attempted forgery from %s detected!",
334          LMAPI->get_var("lcgi-remote-host"));
335       LMAPI->smtp_body_line(forgeWarning);
336       LMAPI->smtp_body_line("");
337       LMAPI->smtp_body_line("A user on the host listed above may have tried to forge credentials to LSG/2 as");
338       LMAPI->smtp_body_line(LMAPI->get_var("lcgi-user"));
339       LMAPI->task_ending();
340       return 0;
341    }
342 
343    if (curcookie || oldcookie) {
344 
345       if (oldcookie) {
346          char *tmp;
347          char *cookieuser;
348          char *cookiehost;
349 
350          tmp = strstr(oldcookie," : ");
351          *tmp = '\0';
352          tmp += 3;
353 
354          if (curcookie ? strcmp(oldcookie,curcookie) != 0 : 0) {
355             lsg2_internal_error("Authorization failed!");
356             return 0;
357          }
358 
359          cookieuser = strchr(tmp,';');
360          cookieuser++;
361          cookiehost = strchr(cookieuser,';');
362          *cookiehost++ = 0;
363 
364          /* This should NEVER, EVER happen.  Ever.  But just to be
365           * paranoid... */
366          if (strcasecmp(cookieuser,fromaddy) != 0) {
367             lsg2_internal_error("Internal authorization routine fault.");
368             return 0;
369          }
370 
371          /* Our hosts don't match, delete old cookie and generate a
372           * new one, then send it to the user. */
373          if (strcasecmp(cookiehost,remote) != 0) {
374              LMAPI->del_cookie(cookiefile,oldcookie);
375              LMAPI->set_var("cookie-for", fromaddy, VAR_TEMP);
376              if(!LMAPI->request_cookie(cookiefile, &cookie[0], 'W', databuf)) {
377                 lsg2_internal_error("Unable to generate authorization code.");
378                 LMAPI->filesys_error(cookiefile);
379                 return 0;
380              }
381              LMAPI->buffer_printf(buffer,length - 1,"%s",cookie);
382              LMAPI->log_printf(2,"CGI: %s logged in (%s)\n",
383                 LMAPI->get_var("lcgi-user"),
384                 LMAPI->get_var("lcgi-remote-host"));
385              if (!LMAPI->get_var("lcgi-pass")) {
386                 LMAPI->set_var("task-form-subject","Web Authorization Code",
387                   VAR_TEMP);
388                 LMAPI->task_heading(fromaddy);
389                 LMAPI->smtp_body_line("Someone, perhaps you, has attempted to log in using your");
390                 LMAPI->smtp_body_line("e-mail address at the mailing list interface at:");
391                 LMAPI->smtp_body_line(LMAPI->get_string("lsg2-cgi-url"));
392                 LMAPI->smtp_body_line("");
393                 LMAPI->smtp_body_line("In order to continue, please paste the following line into");
394                 LMAPI->smtp_body_line("the web form.  (This code will expire if not used.)");
395                 LMAPI->smtp_body_line("");
396                 LMAPI->smtp_body_line(cookie);
397                 LMAPI->smtp_body_line("");
398                 LMAPI->smtp_body_line("Alternatively, you can go to:");
399                 if (!LMAPI->get_var("lcgi-list"))
400                   LMAPI->buffer_printf(databuf, sizeof(databuf) - 1,
401                     "%s/u=%s/m=login/c=%s", LMAPI->get_string("lsg2-cgi-url"),
402                     LMAPI->get_string("lcgi-user"), cookie);
403                 else
404                   LMAPI->buffer_printf(databuf, sizeof(databuf) - 1,
405                     "%s/u=%s/m=listmenu/l=%s/c=%s",
406                     LMAPI->get_string("lsg2-cgi-url"),
407                     LMAPI->get_string("lcgi-user"), LMAPI->get_string("list"),
408                     cookie);
409                 LMAPI->smtp_body_line(databuf);
410                 LMAPI->task_ending();
411                 LMAPI->clean_var("cookie-for", VAR_TEMP);
412                 return 2;
413              } else {
414                 LMAPI->set_var("lcgi-cookie",cookie,VAR_GLOBAL);
415                 return 1;
416              }
417          }
418 
419          /* Are we logging in again from the same machine?  If so,
420           * we just re-mail the cookie. */
421          if (!curcookie && oldcookie) {
422            LMAPI->log_printf(2,"CGI: %s logged in (%s)\n",
423              LMAPI->get_var("lcgi-user"),LMAPI->get_var("lcgi-remote-host"));
424            if (!LMAPI->get_var("lcgi-pass")) {
425               LMAPI->set_var("task-form-subject","Web Authorization Code (Re-mail)",
426                  VAR_TEMP);
427               LMAPI->task_heading(fromaddy);
428               LMAPI->smtp_body_line("Someone, perhaps you, has attempted to log in using your");
429               LMAPI->smtp_body_line("e-mail address at the mailing list interface at:");
430               LMAPI->smtp_body_line(LMAPI->get_string("lsg2-cgi-url"));
431               LMAPI->smtp_body_line("");
432               LMAPI->smtp_body_line("As you already had an authorization code that had not yet");
433               LMAPI->smtp_body_line("expired, it is being re-mailed to you.");
434               LMAPI->smtp_body_line("");
435               LMAPI->smtp_body_line("In order to continue, please paste the following line into");
436               LMAPI->smtp_body_line("the web form.  (This code will expire if not used.)");
437               LMAPI->smtp_body_line("");
438               LMAPI->smtp_body_line(oldcookie);
439               LMAPI->smtp_body_line("");
440               LMAPI->smtp_body_line("Alternatively, you can go to:");
441               if (!LMAPI->get_var("lcgi-list"))
442                   LMAPI->buffer_printf(databuf, sizeof(databuf) - 1,
443                     "%s/u=%s/m=login/c=%s", LMAPI->get_string("lsg2-cgi-url"),
444                     LMAPI->get_string("lcgi-user"), oldcookie);
445               else
446                   LMAPI->buffer_printf(databuf, sizeof(databuf) - 1,
447                     "%s/u=%s/m=listmenu/l=%s/c=%s",
448                     LMAPI->get_string("lsg2-cgi-url"),
449                     LMAPI->get_string("lcgi-user"), LMAPI->get_string("lcgi-list"),
450                     oldcookie);
451               LMAPI->smtp_body_line(databuf);
452               LMAPI->task_ending();
453               LMAPI->clean_var("cookie-for", VAR_TEMP);
454               free((char *)oldcookie);
455               return 2;
456             } else {
457               LMAPI->set_var("lcgi-cookie",oldcookie,VAR_GLOBAL);
458               return 1;
459             }
460          }
461 
462          if (!LMAPI->modify_cookie(cookiefile, oldcookie, databuf)) {
463              lsg2_internal_error("Unable to renew authorization.");
464              LMAPI->filesys_error(cookiefile);
465              free((char *)oldcookie);
466              return 0;
467          }
468          LMAPI->buffer_printf(buffer,length - 1,"%s",oldcookie);
469          free((char *)oldcookie);
470          return 1;
471       } else {
472          lsg2_internal_error("Authorization failed: cookie expired?");
473          return 0;
474       }
475    } else {
476         LMAPI->set_var("cookie-for", fromaddy, VAR_TEMP);
477         if(!LMAPI->request_cookie(cookiefile, &cookie[0], 'W', databuf)) {
478             lsg2_internal_error("Unable to generate authorization code.");
479             LMAPI->filesys_error(cookiefile);
480             return 0;
481         }
482         LMAPI->buffer_printf(buffer,length - 1,"%s",cookie);
483         LMAPI->log_printf(2,"CGI: %s logged in (%s)\n",
484           LMAPI->get_var("lcgi-user"),LMAPI->get_var("lcgi-remote-host"));
485         if (!LMAPI->get_var("lcgi-pass")) {
486            LMAPI->set_var("task-form-subject","Web Authorization Code",
487              VAR_TEMP);
488            LMAPI->task_heading(fromaddy);
489            LMAPI->smtp_body_line("Someone, perhaps you, has attempted to log in using your");
490            LMAPI->smtp_body_line("e-mail address at the mailing list interface at:");
491            LMAPI->smtp_body_line(LMAPI->get_string("lsg2-cgi-url"));
492            LMAPI->smtp_body_line("");
493            LMAPI->smtp_body_line("In order to continue, please paste the following line into");
494            LMAPI->smtp_body_line("the web form.  (This code will expire if not used.)");
495            LMAPI->smtp_body_line("");
496            LMAPI->smtp_body_line(cookie);
497            LMAPI->smtp_body_line("");
498            LMAPI->smtp_body_line("Alternatively, you can go to:");
499            if (!LMAPI->get_var("lcgi-list"))
500                   LMAPI->buffer_printf(databuf, sizeof(databuf) - 1,
501                     "%s/u=%s/m=login/c=%s", LMAPI->get_string("lsg2-cgi-url"),
502                     LMAPI->get_string("lcgi-user"), cookie);
503            else
504                   LMAPI->buffer_printf(databuf, sizeof(databuf) - 1,
505                     "%s/u=%s/m=listmenu/l=%s/c=%s",
506                     LMAPI->get_string("lsg2-cgi-url"),
507                     LMAPI->get_string("lcgi-user"), LMAPI->get_string("lcgi-list"),
508                     cookie);
509            LMAPI->smtp_body_line(databuf);
510            LMAPI->task_ending();
511            LMAPI->clean_var("cookie-for", VAR_TEMP);
512            return 2;
513         } else {
514            LMAPI->set_var("lcgi-cookie",cookie,VAR_GLOBAL);
515            return 1;
516         }
517    }
518 }
519 
lsg2_html_textfile(const char * filename)520 void lsg2_html_textfile(const char *filename)
521 {
522    char buffer[BIG_BUF];
523    FILE *infile;
524    int inchar;
525 
526    LMAPI->buffer_printf(buffer, sizeof(buffer) - 1, "%s.unparsetext", LMAPI->get_string("queuefile"));
527 
528    if (!LMAPI->liscript_parse_file(filename,buffer)) {
529       printf("<P><font size=+2><b>Unable to display textfile!</b></font></P>\n");
530       printf("<!-- Error on file: %s or %s -->\n", filename, buffer);
531       LMAPI->unlink_file(buffer);
532       return;
533    }
534 
535    if ((infile = LMAPI->open_file(buffer,"r")) == NULL) {
536       printf("<P><font size=+2><b>Unable to display textfile!</b></font></P>\n");
537       printf("<!-- Error on file: %s -->\n", buffer);
538       LMAPI->unlink_file(buffer);
539       return;
540    }
541 
542    printf("<pre>");
543    while((inchar = fgetc(infile)) != EOF) {
544       if ((char)inchar == '<') printf("&lt;");
545       else if ((char)inchar == '>') printf("&gt;");
546       else printf("%c", (char)inchar);
547    }
548    printf("</pre>");
549 
550    LMAPI->close_file(infile);
551    LMAPI->unlink_file(buffer);
552 }
553 
lsg2_validate(char * buffer,int length)554 int lsg2_validate(char *buffer, int length)
555 {
556    const char *fromaddy;
557    const char *pass;
558 
559    fromaddy = LMAPI->get_var("lcgi-user");
560    pass = LMAPI->get_var("lcgi-pass");
561 
562    if (!fromaddy) {
563       lsg2_internal_error("No username provided!");
564       return -1;
565    }
566    if (!pass || (strcasecmp(LMAPI->get_string("lcgi-mode"),"passwd") == 0)) {
567 
568       if (strcasecmp(LMAPI->get_string("lcgi-mode"),"passwd") == 0) {
569           if (!LMAPI->get_var("lcgi-cookie")) {
570              lsg2_internal_error("Your template did not provide a cookie with a password change.  Please have your admin update their templates.");
571              return 0;
572           }
573       }
574 
575       switch(lsg2_update_cookie(buffer, length)) {
576         case 1:
577           return 1;
578         case 2:
579           if (!LMAPI->cgi_unparse_template("logincookie")) {
580              lsg2_internal_error("No login cookie template.");
581           }
582           return 0;
583           break;
584         default:
585           return 0;
586       }
587    } else {
588       if (LMAPI->find_pass(fromaddy)) {
589          if (LMAPI->check_pass(fromaddy, pass)) {
590             switch(lsg2_update_cookie(buffer, length)) {
591               case 1:
592                 return 1;
593               case 2:
594                 if (LMAPI->cgi_unparse_template("logincookie")) {
595                    lsg2_internal_error("No login cookie template.");
596                 }
597                 return 0;
598                 break;
599               default:
600                 return 0;
601             }
602          } else {
603             lsg2_internal_error("Incorrect password.");
604             return 0;
605          }
606       } else {
607          lsg2_internal_error("No password set for user.  Provide blank password and check e-mail for validation.");
608          return 0;
609       }
610    }
611 }
612