解决boa网页操作出现502 Bad Gateway The CGI was not CGI/1.1 compliant的一种可能
最近在把一套网页操作的接口从原来Android5.0上移植到Android7.0上。
客户端连接验证的时候主页显示异常
502 Bad Gateway The CGI was not CGI/1.1 compliant
从板子的串口上看到log显示为
[02/Jan/1970:17:13:49 +0000] cgi_header: unable to find LFLF
这部分log为boa里面打印出来的,跟踪源码在cgi_header.c关键函数如下
45 /* TODO: 46 We still need to cycle through the data before the end of the headers, 47 line-by-line, and check for any problems with the CGI 48 outputting overriding http responses, etc... 49 */ 50 51 int process_cgi_header(request * req) 52 { 53 char *buf; 54 char *c; 55 56 if (req->cgi_status != CGI_DONE) 57 req->cgi_status = CGI_BUFFER; 58 59 buf = req->header_line; 60 61 c = strstr(buf, "\n\r\n"); 62 if (c == NULL) { 63 c = strstr(buf, "\n\n"); 64 if (c == NULL) { 65 log_error_time(); 66 fputs("cgi_header: unable to find LFLF\n", stderr); 67 #ifdef FASCIST_LOGGING 68 log_error_time(); 69 fprintf(stderr, "\"%s\"\n", buf); 70 #endif 71 send_r_bad_gateway(req); 72 return 0; 73 } 74 } 75 if (req->simple) { 76 if (*(c + 1) == '\r') 77 req->header_line = c + 2; 78 else 79 req->header_line = c + 1; 80 return 1; 81 }
该函数
process_cgi_header
是boa父进程fork出子进去去执行对应的cgi程序后,通过管道来获取cgi执行的输出内容进行解析,从而对客户端进行相应。
在src/pipe.c调用如下:
27 /* 28 * Name: read_from_pipe 29 * Description: Reads data from a pipe 30 * 31 * Return values: 32 * -1: request blocked, move to blocked queue 33 * 0: EOF or error, close it down 34 * 1: successful read, recycle in ready queue 35 */ 36 37 int read_from_pipe(request * req) 38 { 39 int bytes_read, bytes_to_read = 40 BUFFER_SIZE - (req->header_end - req->buffer); 41 42 if (bytes_to_read == 0) { /* buffer full */ 43 if (req->cgi_status == CGI_PARSE) { /* got+parsed header */ 44 req->cgi_status = CGI_BUFFER; 45 *req->header_end = '\0'; /* points to end of read data */ 46 /* Could the above statement overwrite data??? 47 No, because req->header_end points to where new data 48 should begin, not where old data is. 49 */ 50 return process_cgi_header(req); /* cgi_status will change */ 51 } 52 req->status = PIPE_WRITE; 53 return 1; 54 } 55 56 bytes_read = read(req->data_fd, req->header_end, bytes_to_read); 57 #ifdef FASCIST_LOGGING 58 if (bytes_read > 0) { 59 *(req->header_end + bytes_read) = '\0'; 60 fprintf(stderr, "pipe.c - read %d bytes: \"%s\"\n", 61 bytes_read, req->header_end); 62 } else 63 fprintf(stderr, "pipe.c - read %d bytes\n", bytes_read); 64 fprintf(stderr, "status, cgi_status: %d, %d\n", req->status, 65 req->cgi_status); 66 #endif 67 68 if (bytes_read == -1) { 69 if (errno == EINTR) 70 return 1; 71 else if (errno == EWOULDBLOCK || errno == EAGAIN) 72 return -1; /* request blocked at the pipe level, but keep going */ 73 else { 74 req->status = DEAD; 75 log_error_doc(req); 76 perror("pipe read"); 77 return 0; 78 } 79 } else if (bytes_read == 0) { /* eof, write rest of buffer */ 80 req->status = PIPE_WRITE; 81 if (req->cgi_status == CGI_PARSE) { /* hasn't processed header yet */ 82 req->cgi_status = CGI_DONE; 83 *req->header_end = '\0'; /* points to end of read data */ 84 return process_cgi_header(req); /* cgi_status will change */ 85 } 86 req->cgi_status = CGI_DONE; 87 return 1; 88 } 89 req->header_end += bytes_read; 90 return 1; 91 } 92
这部分代码中有一个宏
FASCIST_LOGGING
加上这个宏重新编译验证可以看到更信息的log
[02/Jan/1970:17:13:49 +0000] external/boa/src/request.c:662 - Parsing "Host: 192.168.49.1" [02/Jan/1970:17:13:49 +0000] external/boa/src/request.c:662 - Parsing "Upgrade-Insecure-Requests: 1" [02/Jan/1970:17:13:49 +0000] external/boa/src/request.c:662 - Parsing "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" [02/Jan/1970:17:13:49 +0000] external/boa/src/request.c:662 - Parsing "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1" [02/Jan/1970:17:13:49 +0000] external/boa/src/request.c:662 - Parsing "Accept-Language: zh-cn" [02/Jan/1970:17:13:49 +0000] external/boa/src/request.c:662 - Parsing "Accept-Encoding: gzip, deflate" [02/Jan/1970:17:13:49 +0000] external/boa/src/request.c:662 - Parsing "Connection: keep-alive" external/boa/src/alias.c:150 - comparing "/cgi-bin/home.cgi" (request) to "/cgi-bin/" (alias): Got it! [02/Jan/1970:17:13:49 +0000] external/boa/src/alias.c:414 - pathname in init_script_alias is: "/data/boa/www/cgi-bin/home.cgi" ("home.cgi") external/boa/src/cgi.c - environment variable for cgi: "PATH=/bin:/usr/bin:/usr/local/bin" external/boa/src/cgi.c - environment variable for cgi: "SERVER_SOFTWARE=Boa/0.94.13" external/boa/src/cgi.c - environment variable for cgi: "SERVER_NAME=www.lollipop.com" external/boa/src/cgi.c - environment variable for cgi: "GATEWAY_INTERFACE=CGI/1.1" external/boa/src/cgi.c - environment variable for cgi: "SERVER_PORT=80" external/boa/src/cgi.c - environment variable for cgi: "SERVER_ADMIN=" external/boa/src/cgi.c - environment variable for cgi: "HTTP_HOST=192.168.49.1" external/boa/src/cgi.c - environment variable for cgi: "HTTP_UPGRADE_INSECURE_REQUESTS=1" external/boa/src/cgi.c - environment variable for cgi: "HTTP_USER_AGENT=Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1" external/boa/src/cgi.c - environment variable for cgi: "HTTP_ACCEPT_LANGUAGE=zh-cn" external/boa/src/cgi.c - environment variable for cgi: "HTTP_ACCEPT_ENCODING=gzip, deflate" external/boa/src/cgi.c - environment variable for cgi: "REQUEST_METHOD=GET" external/boa/src/cgi.c - environment variable for cgi: "SERVER_ADDR=192.168.49.1" external/boa/src/cgi.c - environment variable for cgi: "SERVER_PROTOCOL=HTTP/1.1" external/boa/src/cgi.c - environment variable for cgi: "REQUEST_URI=/cgi-bin/home.cgi" external/boa/src/cgi.c - environment variable for cgi: "SCRIPT_NAME=/cgi-bin/home.cgi" external/boa/src/cgi.c - environment variable for cgi: "REMOTE_ADDR=192.168.49.77" external/boa/src/cgi.c - environment variable for cgi: "REMOTE_PORT=54608" pipe.c - read -1 bytes status, cgi_status: 7, 1 pipe.c - read 0 bytes status, cgi_status: 7, 1 [02/Jan/1970:17:13:49 +0000] cgi_header: unable to find LFLF [02/Jan/1970:17:13:49 +0000] "" [02/Jan/1970:17:13:49 +0000] external/boa/src/buffer.c:200 - Wrote "HTTP/1.0 502 Bad Gateway Date: Fri, 02 Jan 1970 17:13:49 GMT Server: Boa/0.94.13 Connection: close Content-Type: text/html; charset=ISO-8859-1 <HTML><HEAD><TITLE>502 Bad Gateway</TITLE></HEAD> <BODY><H1>502 Bad Gateway</H1> The CGI was not CGI/1.1 compliant. </BODY></HTML> " (281 bytes)
从log上看出读取pipe的时候第一次读取失败返回来-1,第二次读取到0 说明管道里没有读取到东西,可能是cgi没有往管道里写东西。
查看boa是如何执行对应的cgi并从cgi获取输出信息的
在cgi.c里面有如下代码
346 /* 347 * Name: init_cgi 348 * 349 * Description: Called for GET/POST requests that refer to ScriptAlias 350 * directories or application/x-httpd-cgi files. Ties stdout to socket, 351 * stdin to data if POST, and execs CGI. 352 * stderr remains tied to our log file; is this good? 353 * 354 * Returns: 355 * 0 - error or NPH, either way the socket is closed 356 * 1 - success 357 */ 358 359 int init_cgi(request * req) 360 { 361 int child_pid; 362 int pipes[2]; 363 int use_pipes = 0; 364 365 SQUASH_KA(req); 366 367 if (req->is_cgi) { 368 if (complete_env(req) == 0) { 369 return 0; 370 } 371 } 372 #ifdef FASCIST_LOGGING 373 { 374 int i; 375 for (i = 0; i < req->cgi_env_index; ++i) 376 fprintf(stderr, "%s - environment variable for cgi: \"%s\"\n", 377 __FILE__, req->cgi_env[i]); 378 } 379 #endif 380 381 if (req->is_cgi == CGI || 1) { 382 use_pipes = 1; 383 if (pipe(pipes) == -1) { 384 log_error_time(); 385 perror("pipe"); 386 return 0; 387 } 388 389 /* set the read end of the socket to non-blocking */ 390 if (set_nonblock_fd(pipes[0]) == -1) { 391 log_error_time(); 392 perror("cgi-fcntl"); 393 close(pipes[0]); 394 close(pipes[1]); 395 return 0; 396 } 397 } 398 399 child_pid = fork(); 400 switch(child_pid) { 401 case -1: 402 /* fork unsuccessful */ 403 log_error_time(); 404 perror("fork"); 405 406 if (use_pipes) { 407 close(pipes[0]); 408 close(pipes[1]); 409 } 410 send_r_error(req); 411 /* FIXME: There is aproblem here. send_r_error would work 412 for NPH and CGI, but not for GUNZIP. Fix that. */ 413 /* i'd like to send_r_error, but.... */ 414 return 0; 415 break; 416 case 0: 417 /* child */ 418 if (req->is_cgi == CGI || req->is_cgi == NPH) { 419 char *foo = strdup(req->pathname); 420 char *c; 421 422 if (!foo) { 423 WARN("unable to strdup pathname for req->pathname"); 424 _exit(1); 425 } 426 c = strrchr(foo, '/'); 427 if (c) { 428 ++c; 429 *c = '\0'; 430 } else { 431 /* we have a serious problem */ 432 log_error_time(); 433 perror("chdir"); 434 if (use_pipes) 435 close(pipes[1]); 436 _exit(1); 437 } 438 if (chdir(foo) != 0) { 439 log_error_time(); 440 perror("chdir"); 441 if (use_pipes) 442 close(pipes[1]); 443 _exit(1); 444 } 445 } 446 if (use_pipes) { 447 close(pipes[0]); 448 /* tie cgi's STDOUT to it's write end of pipe */ 449 if (dup2(pipes[1], STDOUT_FILENO) == -1) { 450 log_error_time(); 451 perror("dup2 - pipes"); 452 close(pipes[1]); 453 _exit(1); 454 } 455 close(pipes[1]); 456 if (set_block_fd(STDOUT_FILENO) == -1) { 457 log_error_time(); 458 perror("cgi-fcntl"); 459 _exit(1); 460 } 461 } else { 462 /* tie stdout to socket */ 463 if (dup2(req->fd, STDOUT_FILENO) == -1) { 464 log_error_time(); 465 perror("dup2 - fd"); 466 _exit(1); 467 } 468 /* Switch socket flags back to blocking */ 469 if (set_block_fd(req->fd) == -1) { 470 log_error_time(); 471 perror("cgi-fcntl"); 472 _exit(1); 473 } 474 } 475 /* tie post_data_fd to POST stdin */ 476 if (req->method == M_POST) { /* tie stdin to file */ 477 lseek(req->post_data_fd, SEEK_SET, 0); 478 dup2(req->post_data_fd, STDIN_FILENO); 479 close(req->post_data_fd); 480 } 481 /* Close access log, so CGI program can't scribble 482 * where it shouldn't 483 */ 484 close_access_log(); 485 486 /* 487 * tie STDERR to cgi_log_fd 488 * cgi_log_fd will automatically close, close-on-exec rocks! 489 * if we don't tied STDERR (current log_error) to cgi_log_fd, 490 * then we ought to close it. 491 */ 492 if (!cgi_log_fd) 493 dup2(devnullfd, STDERR_FILENO); 494 else 495 dup2(cgi_log_fd, STDERR_FILENO); 496 497 if (req->is_cgi) { 498 char *aargv[CGI_ARGC_MAX + 1]; 499 create_argv(req, aargv); 500 execve(req->pathname, aargv, req->cgi_env); 501 } else { 502 if (req->pathname[strlen(req->pathname) - 1] == '/') 503 execl(dirmaker, dirmaker, req->pathname, req->request_uri, 504 NULL); 505 #ifdef GUNZIP 506 else 507 execl(GUNZIP, GUNZIP, "--stdout", "--decompress", 508 req->pathname, NULL); 509 #endif 510 } 511 /* execve failed */ 512 WARN(req->pathname); 513 _exit(1); 514 break; 515 516 default: 517 /* parent */ 518 /* if here, fork was successful */ 519 if (verbose_cgi_logs) { 520 log_error_time(); 521 fprintf(stderr, "Forked child \"%s\" pid %d\n", 522 req->pathname, child_pid); 523 } 524 525 if (req->method == M_POST) { 526 close(req->post_data_fd); /* child closed it too */ 527 req->post_data_fd = 0; 528 } 529 530 /* NPH, GUNZIP, etc... all go straight to the fd */ 531 if (!use_pipes) 532 return 0; 533 534 close(pipes[1]); 535 req->data_fd = pipes[0]; 536 537 req->status = PIPE_READ; 538 if (req->is_cgi == CGI) { 539 req->cgi_status = CGI_PARSE; /* got to parse cgi header */ 540 /* for cgi_header... I get half the buffer! */ 541 req->header_line = req->header_end = 542 (req->buffer + BUFFER_SIZE / 2); 543 } else { 544 req->cgi_status = CGI_BUFFER; 545 /* I get all the buffer! */ 546 req->header_line = req->header_end = req->buffer; 547 } 548 549 /* reset req->filepos for logging (it's used in pipe.c) */ 550 /* still don't know why req->filesize might be reset though */ 551 req->filepos = 0; 552 break; 553 } 554 555 return 1; 556 }
从这部分代码可以知道boa在fork出子进程去执行对应的cgi的时候将子进程的标准输出dup到了提前创建好的pipe,父进程则从pipe的另一端读取cgi的输出内容(既cgi程序printf输出的内容)。
现在需要去看下对应的cgi是否有内容输出来
home.c编译成home.cgi
main函数如下
116 int main(void) 117 { 118 char mode[32] = {0}; 119 120 memset(mode, 0, sizeof(mode)); 121 lollipopConfInit(); 122 getLollipopConf("function_mode", mode); 123 lollipopConfDeinit(); 124 125 126 if (!strcmp(mode, "WFD")) { 127 return p2p_state(); 128 } else if (!strcmp(mode, "DLNA") || !strcmp(mode, "MIX")) { 129 return ap_state(); 130 } else { 131 return -1; 132 } 133 }
24 int p2p_state(void) 25 { 26 struct list_head *pList; 27 struct list_head groupList; 28 29 printf("Content-type: text/html\n\n"); 30 printf("<HTML><BODY>\n"); 31 printf("<HEAD>\n"); 32 printf("<TITLE>Lollipop Home</TITLE>\n"); 33 printf("<link rel='stylesheet' type='text/css' href='css/main.css' />"); 34 printf("</HEAD>\n"); 35 36 printf("<p>"); 37 printf("<a href=\"wifi.cgi\"><img border=\"0\" src=\"/res/icon_wifi_01.png\" /></a>\n"); 38 printf("touch icon to show wifi direct group list"); 39 printf("</p>"); 40 printf("<br />\n"); 41 printf("<br />\n"); ........... 73 int ap_state(void) 74 { 75 struct list_head apList; 76 struct list_head *pList; 77 78 printf("Content-type:text/html;charset=utf-8\n\n"); //response header 79 printf("<HTML><BODY>\n"); 80 printf("<HEAD>\n"); 81 printf("<TITLE>Lollipop Home</TITLE>\n"); 82 printf("<link rel='stylesheet' type='text/css' href='/css/main.css' />"); 83 printf("</HEAD>\n"); 84 85 printf("<div class='content_icon'>\n"); 86 printf("<p><a href='wifi.cgi' class='icon_wifi'>WiFi AP</a></p>\n"); 87 printf("</div>\n\n"); 88 89 .....
在这里需要获取mode类型,然后输出不同的内容
检查过p2p_state()及ap_state()函数都是由正常的格式内容打印出来,而且是在原来的平台验证过的.
但是main里面还有一个else的分支简单粗暴的返回了一个 -1.
添加一个log信息到else信息里重新验证了一次,果然main函数跑到了else分支,也就是直接返回-1而没有printf任何内容,这样boa进程则无法从home.cgi读取到任何输出内容。
作为一个cgi程序响应客户端的GET请求,如果不是正确的请求个人认为至少应该回复一组正确的消息,只是在内容上可以显示一些提示信息,而不能像普通的程序一样直接返回一个-1.
这会导致客户端看到的错误信息与你想表达的意思不一致。
所以在这只需要添加一个notFound的返回消息
141 void not_found(void) 142 { 143 printf("Content-type:text/html;charset=utf-8\n\n"); //response header 144 printf("<HTML><BODY>\n"); 145 printf("<HEAD>\n"); 146 printf("<TITLE>Lollipop Home</TITLE>\n"); 147 printf("<link rel='stylesheet' type='text/css' href='/css/main.css' />"); 148 printf("</HEAD>\n"); 149 150 printf("<p>Not Support Mode</a></p>\n"); 151 152 printf("</BODY>\n"); 153 printf("</HTML>\n"); 154 155 } 156 157 int main(void) 158 { 159 char mode[32] = {0}; 160 161 memset(mode, 0, sizeof(mode)); 162 lollipopConfInit(); 163 getLollipopConf("function_mode", mode); 164 lollipopConfDeinit(); 165 if (!strcmp(mode, "WFD")) { 166 return p2p_state(); 167 } else if (!strcmp(mode, "DLNA") || !strcmp(mode, "MIX") || !strcmp(mode, "AIRPLAY")) { 168 return ap_state(); 169 } else { 170 not_found(); 171 } 172 return 0; 173 }