• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/cgi-io/main.c

  1 /*
  2  * cgi-io - LuCI non-RPC helper
  3  *
  4  *   Copyright (C) 2013 Jo-Philipp Wich <jo@mein.io>
  5  *
  6  * Permission to use, copy, modify, and/or distribute this software for any
  7  * purpose with or without fee is hereby granted, provided that the above
  8  * copyright notice and this permission notice appear in all copies.
  9  *
 10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 17  */
 18 
 19 #define _GNU_SOURCE /* splice(), SPLICE_F_MORE */
 20 
 21 #include <stdio.h>
 22 #include <stdlib.h>
 23 #include <stdbool.h>
 24 #include <unistd.h>
 25 #include <string.h>
 26 #include <errno.h>
 27 #include <fcntl.h>
 28 #include <ctype.h>
 29 #include <sys/stat.h>
 30 #include <sys/wait.h>
 31 #include <sys/sendfile.h>
 32 #include <sys/ioctl.h>
 33 #include <linux/fs.h>
 34 
 35 #include <libubus.h>
 36 #include <libubox/blobmsg.h>
 37 
 38 #include "util.h"
 39 #include "multipart_parser.h"
 40 
 41 #ifndef O_TMPFILE
 42 #define O_TMPFILE       (020000000 | O_DIRECTORY)
 43 #endif
 44 
 45 #define READ_BLOCK 4096
 46 
 47 enum part {
 48         PART_UNKNOWN,
 49         PART_SESSIONID,
 50         PART_FILENAME,
 51         PART_FILEMODE,
 52         PART_FILEDATA
 53 };
 54 
 55 const char *parts[] = {
 56         "(bug)",
 57         "sessionid",
 58         "filename",
 59         "filemode",
 60         "filedata",
 61 };
 62 
 63 struct state
 64 {
 65         bool is_content_disposition;
 66         enum part parttype;
 67         char *sessionid;
 68         char *filename;
 69         bool filedata;
 70         int filemode;
 71         int filefd;
 72         int tempfd;
 73 };
 74 
 75 static struct state st;
 76 
 77 #ifndef UNIT_TESTING
 78 
 79 enum {
 80         SES_ACCESS,
 81         __SES_MAX,
 82 };
 83 
 84 static const struct blobmsg_policy ses_policy[__SES_MAX] = {
 85         [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
 86 };
 87 
 88 static void
 89 session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
 90 {
 91         struct blob_attr *tb[__SES_MAX];
 92         bool *allow = (bool *)req->priv;
 93 
 94         if (!msg)
 95                 return;
 96 
 97         blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
 98 
 99         if (tb[SES_ACCESS])
100                 *allow = blobmsg_get_bool(tb[SES_ACCESS]);
101 }
102 #endif
103 
104 static bool
105 session_access(const char *sid, const char *scope, const char *obj, const char *func)
106 {
107 #ifdef UNIT_TESTING
108         return true;
109 #else
110         uint32_t id;
111         bool allow = false;
112         struct ubus_context *ctx;
113         static struct blob_buf req;
114 
115         ctx = ubus_connect(NULL);
116 
117         if (!ctx || !obj || ubus_lookup_id(ctx, "session", &id))
118                 goto out;
119 
120         blob_buf_init(&req, 0);
121         blobmsg_add_string(&req, "ubus_rpc_session", sid);
122         blobmsg_add_string(&req, "scope", scope);
123         blobmsg_add_string(&req, "object", obj);
124         blobmsg_add_string(&req, "function", func);
125 
126         ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
127 
128 out:
129         if (ctx)
130                 ubus_free(ctx);
131 
132         return allow;
133 #endif
134 }
135 
136 static char *
137 checksum(const char *applet, size_t sumlen, const char *file)
138 {
139         pid_t pid;
140         int r;
141         int fds[2];
142         static char chksum[65];
143 
144         if (pipe(fds))
145                 return NULL;
146 
147         switch ((pid = fork()))
148         {
149         case -1:
150                 return NULL;
151 
152         case 0:
153                 uloop_done();
154 
155                 dup2(fds[1], 1);
156 
157                 close(0);
158                 close(2);
159                 close(fds[0]);
160                 close(fds[1]);
161 
162                 if (execl("/bin/busybox", "/bin/busybox", applet, file, NULL))
163                         return NULL;
164 
165                 break;
166 
167         default:
168                 memset(chksum, 0, sizeof(chksum));
169                 r = read(fds[0], chksum, sumlen);
170 
171                 waitpid(pid, NULL, 0);
172                 close(fds[0]);
173                 close(fds[1]);
174 
175                 if (r < 0)
176                         return NULL;
177         }
178 
179         return chksum;
180 }
181 
182 static int
183 response(bool success, const char *message)
184 {
185         char *chksum;
186         struct stat s;
187 
188         printf("Status: 200 OK\r\n");
189         printf("Content-Type: text/plain\r\n\r\n{\n");
190 
191         if (success)
192         {
193                 if (!stat(st.filename, &s))
194                         printf("\t\"size\": %u,\n", (unsigned int)s.st_size);
195                 else
196                         printf("\t\"size\": null,\n");
197 
198                 chksum = checksum("md5sum", 32, st.filename);
199                 printf("\t\"checksum\": %s%s%s,\n",
200                         chksum ? "\"" : "",
201                         chksum ? chksum : "null",
202                         chksum ? "\"" : "");
203 
204                 chksum = checksum("sha256sum", 64, st.filename);
205                 printf("\t\"sha256sum\": %s%s%s\n",
206                         chksum ? "\"" : "",
207                         chksum ? chksum : "null",
208                         chksum ? "\"" : "");
209         }
210         else
211         {
212                 if (message)
213                         printf("\t\"message\": \"%s\",\n", message);
214 
215                 printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
216 
217                 if (st.filefd > -1 && st.filename)
218                         unlink(st.filename);
219         }
220 
221         printf("}\n");
222 
223         return -1;
224 }
225 
226 static int
227 failure(int code, int e, const char *message)
228 {
229         printf("Status: %d %s\r\n", code, message);
230         printf("Content-Type: text/plain\r\n\r\n");
231         printf("%s", message);
232 
233         if (e)
234                 printf(": %s", strerror(e));
235 
236         printf("\n");
237 
238         return -1;
239 }
240 
241 static int
242 filecopy(void)
243 {
244         int len;
245         char buf[READ_BLOCK];
246 
247         if (!st.filedata)
248         {
249                 close(st.tempfd);
250                 errno = EINVAL;
251                 return response(false, "No file data received");
252         }
253 
254         snprintf(buf, sizeof(buf), "/proc/self/fd/%d", st.tempfd);
255 
256         if (unlink(st.filename) < 0 && errno != ENOENT)
257         {
258                 close(st.tempfd);
259                 return response(false, "Failed to unlink existing file");
260         }
261 
262         if (linkat(AT_FDCWD, buf, AT_FDCWD, st.filename, AT_SYMLINK_FOLLOW) < 0)
263         {
264                 if (lseek(st.tempfd, 0, SEEK_SET) < 0)
265                 {
266                         close(st.tempfd);
267                         return response(false, "Failed to rewind temp file");
268                 }
269 
270                 st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
271 
272                 if (st.filefd < 0)
273                 {
274                         close(st.tempfd);
275                         return response(false, "Failed to open target file");
276                 }
277 
278                 while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
279                 {
280                         if (write(st.filefd, buf, len) != len)
281                         {
282                                 close(st.tempfd);
283                                 close(st.filefd);
284                                 return response(false, "I/O failure while writing target file");
285                         }
286                 }
287 
288                 close(st.filefd);
289         }
290 
291         close(st.tempfd);
292 
293         if (chmod(st.filename, st.filemode))
294                 return response(false, "Failed to chmod target file");
295 
296         return 0;
297 }
298 
299 static int
300 header_field(multipart_parser *p, const char *data, size_t len)
301 {
302         st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
303         return 0;
304 }
305 
306 static int
307 header_value(multipart_parser *p, const char *data, size_t len)
308 {
309         size_t i, j;
310 
311         if (!st.is_content_disposition)
312                 return 0;
313 
314         if (len < 10 || strncasecmp(data, "form-data", 9))
315                 return 0;
316 
317         for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--);
318 
319         if (len < 8 || strncasecmp(data, "name=\"", 6))
320                 return 0;
321 
322         for (data += 6, len -= 6, i = 0; i <= len; i++)
323         {
324                 if (*(data + i) != '"')
325                         continue;
326 
327                 for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
328                         if (!strncmp(data, parts[j], i))
329                                 st.parttype = j;
330 
331                 break;
332         }
333 
334         return 0;
335 }
336 
337 static int
338 data_begin_cb(multipart_parser *p)
339 {
340         if (st.parttype == PART_FILEDATA)
341         {
342                 if (!st.sessionid)
343                         return response(false, "File data without session");
344 
345                 if (!st.filename)
346                         return response(false, "File data without name");
347 
348                 if (!session_access(st.sessionid, "file", st.filename, "write"))
349                         return response(false, "Access to path denied by ACL");
350 
351                 st.tempfd = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
352 
353                 if (st.tempfd < 0)
354                         return response(false, "Failed to create temporary file");
355         }
356 
357         return 0;
358 }
359 
360 static int
361 data_cb(multipart_parser *p, const char *data, size_t len)
362 {
363         int wlen = len;
364 
365         switch (st.parttype)
366         {
367         case PART_SESSIONID:
368                 st.sessionid = datadup(data, len);
369                 break;
370 
371         case PART_FILENAME:
372                 st.filename = canonicalize_path(data, len);
373                 break;
374 
375         case PART_FILEMODE:
376                 st.filemode = strtoul(data, NULL, 8);
377                 break;
378 
379         case PART_FILEDATA:
380                 if (write(st.tempfd, data, len) != wlen)
381                 {
382                         close(st.tempfd);
383                         return response(false, "I/O failure while writing temporary file");
384                 }
385 
386                 if (!st.filedata)
387                         st.filedata = !!wlen;
388 
389                 break;
390 
391         default:
392                 break;
393         }
394 
395         return 0;
396 }
397 
398 static int
399 data_end_cb(multipart_parser *p)
400 {
401         if (st.parttype == PART_SESSIONID)
402         {
403                 if (!session_access(st.sessionid, "cgi-io", "upload", "write"))
404                 {
405                         errno = EPERM;
406                         return response(false, "Upload permission denied");
407                 }
408         }
409         else if (st.parttype == PART_FILEDATA)
410         {
411                 if (st.tempfd < 0)
412                         return response(false, "Internal program failure");
413 
414 #if 0
415                 /* prepare directory */
416                 for (ptr = st.filename; *ptr; ptr++)
417                 {
418                         if (*ptr == '/')
419                         {
420                                 *ptr = 0;
421 
422                                 if (mkdir(st.filename, 0755))
423                                 {
424                                         unlink(st.tmpname);
425                                         return response(false, "Failed to create destination directory");
426                                 }
427 
428                                 *ptr = '/';
429                         }
430                 }
431 #endif
432 
433                 if (filecopy())
434                         return -1;
435 
436                 return response(true, NULL);
437         }
438 
439         st.parttype = PART_UNKNOWN;
440         return 0;
441 }
442 
443 static multipart_parser *
444 init_parser(void)
445 {
446         char *boundary;
447         const char *var;
448 
449         multipart_parser *p;
450         static multipart_parser_settings s = {
451                 .on_part_data        = data_cb,
452                 .on_headers_complete = data_begin_cb,
453                 .on_part_data_end    = data_end_cb,
454                 .on_header_field     = header_field,
455                 .on_header_value     = header_value
456         };
457 
458         var = getenv("CONTENT_TYPE");
459 
460         if (!var || strncmp(var, "multipart/form-data;", 20))
461                 return NULL;
462 
463         for (var += 20; *var && *var != '='; var++);
464 
465         if (*var++ != '=')
466                 return NULL;
467 
468         boundary = malloc(strlen(var) + 3);
469 
470         if (!boundary)
471                 return NULL;
472 
473         strcpy(boundary, "--");
474         strcpy(boundary + 2, var);
475 
476         st.tempfd = -1;
477         st.filefd = -1;
478         st.filemode = 0600;
479 
480         p = multipart_parser_init(boundary, &s);
481 
482         free(boundary);
483 
484         return p;
485 }
486 
487 static int
488 main_upload(int argc, char *argv[])
489 {
490         int rem, len;
491         bool done = false;
492         char buf[READ_BLOCK];
493         multipart_parser *p;
494 
495         p = init_parser();
496 
497         if (!p)
498         {
499                 errno = EINVAL;
500                 return response(false, "Invalid request");
501         }
502 
503         while ((len = read(0, buf, sizeof(buf))) > 0)
504         {
505                 if (!done) {
506                         rem = multipart_parser_execute(p, buf, len);
507                         done = (rem < len);
508                 }
509         }
510 
511         multipart_parser_free(p);
512 
513         return 0;
514 }
515 
516 static void
517 free_charp(char **ptr)
518 {
519         free(*ptr);
520 }
521 
522 #define autochar __attribute__((__cleanup__(free_charp))) char
523 
524 static int
525 main_download(int argc, char **argv)
526 {
527         char *fields[] = { "sessionid", NULL, "path", NULL, "filename", NULL, "mimetype", NULL };
528         unsigned long long size = 0;
529         char *p, buf[READ_BLOCK];
530         ssize_t len = 0;
531         struct stat s;
532         int rfd;
533 
534         autochar *post = postdecode(fields, 4);
535         (void) post;
536 
537         if (!fields[1] || !session_access(fields[1], "cgi-io", "download", "read"))
538                 return failure(403, 0, "Download permission denied");
539 
540         if (!fields[3] || !session_access(fields[1], "file", fields[3], "read"))
541                 return failure(403, 0, "Access to path denied by ACL");
542 
543         if (stat(fields[3], &s))
544                 return failure(404, errno, "Failed to stat requested path");
545 
546         if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
547                 return failure(403, 0, "Requested path is not a regular file or block device");
548 
549         for (p = fields[5]; p && *p; p++)
550                 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
551                         return failure(400, 0, "Invalid characters in filename");
552 
553         for (p = fields[7]; p && *p; p++)
554                 if (!isalnum(*p) && !strchr(" .;=/-", *p))
555                         return failure(400, 0, "Invalid characters in mimetype");
556 
557         rfd = open(fields[3], O_RDONLY);
558 
559         if (rfd < 0)
560                 return failure(500, errno, "Failed to open requested path");
561 
562         if (S_ISBLK(s.st_mode))
563                 ioctl(rfd, BLKGETSIZE64, &size);
564         else
565                 size = (unsigned long long)s.st_size;
566 
567         printf("Status: 200 OK\r\n");
568         printf("Content-Type: %s\r\n", fields[7] ? fields[7] : "application/octet-stream");
569 
570         if (fields[5])
571                 printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields[5]);
572 
573         if (size > 0) {
574                 printf("Content-Length: %llu\r\n\r\n", size);
575                 fflush(stdout);
576 
577                 while (size > 0) {
578                         len = sendfile(1, rfd, NULL, size);
579 
580                         if (len == -1) {
581                                 if (errno == ENOSYS || errno == EINVAL) {
582                                         while ((len = read(rfd, buf, sizeof(buf))) > 0)
583                                                 fwrite(buf, len, 1, stdout);
584 
585                                         fflush(stdout);
586                                         break;
587                                 }
588 
589                                 if (errno == EINTR || errno == EAGAIN)
590                                         continue;
591                         }
592 
593                         if (len <= 0)
594                                 break;
595 
596                         size -= len;
597                 }
598         }
599         else {
600                 printf("\r\n");
601 
602                 while ((len = read(rfd, buf, sizeof(buf))) > 0)
603                         fwrite(buf, len, 1, stdout);
604 
605                 fflush(stdout);
606         }
607 
608         close(rfd);
609 
610         return 0;
611 }
612 
613 static int
614 main_backup(int argc, char **argv)
615 {
616         pid_t pid;
617         time_t now;
618         int r;
619         int len;
620         int status;
621         int fds[2];
622         char datestr[16] = { 0 };
623         char hostname[64] = { 0 };
624         char *fields[] = { "sessionid", NULL };
625 
626         autochar *post = postdecode(fields, 1);
627         (void) post;
628 
629         if (!fields[1] || !session_access(fields[1], "cgi-io", "backup", "read"))
630                 return failure(403, 0, "Backup permission denied");
631 
632         if (pipe(fds))
633                 return failure(500, errno, "Failed to spawn pipe");
634 
635         switch ((pid = fork()))
636         {
637         case -1:
638                 return failure(500, errno, "Failed to fork process");
639 
640         case 0:
641                 dup2(fds[1], 1);
642 
643                 close(0);
644                 close(2);
645                 close(fds[0]);
646                 close(fds[1]);
647 
648                 r = chdir("/");
649                 if (r < 0)
650                         return failure(500, errno, "Failed chdir('/')");
651 
652                 execl("/sbin/sysupgrade", "/sbin/sysupgrade",
653                       "--create-backup", "-", NULL);
654 
655                 return -1;
656 
657         default:
658                 close(fds[1]);
659 
660                 now = time(NULL);
661                 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
662 
663                 if (gethostname(hostname, sizeof(hostname) - 1))
664                         sprintf(hostname, "OpenWrt");
665 
666                 printf("Status: 200 OK\r\n");
667                 printf("Content-Type: application/x-targz\r\n");
668                 printf("Content-Disposition: attachment; "
669                        "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
670 
671                 fflush(stdout);
672 
673                 do {
674                         len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
675                 } while (len > 0 || (len == -1 && errno == EINTR));
676 
677                 waitpid(pid, &status, 0);
678 
679                 close(fds[0]);
680 
681                 return 0;
682         }
683 }
684 
685 
686 static const char *
687 lookup_executable(const char *cmd)
688 {
689         size_t plen = 0, clen;
690         static char path[PATH_MAX];
691         char *search, *p;
692         struct stat s;
693 
694         if (!cmd)
695                 return NULL;
696 
697         clen = strlen(cmd) + 1;
698 
699         if (!stat(cmd, &s) && S_ISREG(s.st_mode))
700                 return cmd;
701 
702         search = getenv("PATH");
703 
704         if (!search)
705                 search = "/bin:/usr/bin:/sbin:/usr/sbin";
706 
707         p = search;
708 
709         do {
710                 if (*p != ':' && *p != '\0')
711                         continue;
712 
713                 plen = p - search;
714 
715                 if ((plen + clen) >= sizeof(path))
716                         continue;
717 
718                 strncpy(path, search, plen);
719                 sprintf(path + plen, "/%s", cmd);
720 
721                 if (!stat(path, &s) && S_ISREG(s.st_mode))
722                         return path;
723 
724                 search = p + 1;
725         } while (*p++);
726 
727         return NULL;
728 }
729 
730 static int
731 main_exec(int argc, char **argv)
732 {
733         char *fields[] = { "sessionid", NULL, "command", NULL, "filename", NULL, "mimetype", NULL };
734         int i, devnull, status, fds[2];
735         bool allowed = false;
736         ssize_t len = 0;
737         const char *exe;
738         char *p, **args;
739         pid_t pid;
740 
741         autochar *post = postdecode(fields, 4);
742         (void) post;
743 
744         if (!fields[1] || !session_access(fields[1], "cgi-io", "exec", "read"))
745                 return failure(403, 0, "Exec permission denied");
746 
747         for (p = fields[5]; p && *p; p++)
748                 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
749                         return failure(400, 0, "Invalid characters in filename");
750 
751         for (p = fields[7]; p && *p; p++)
752                 if (!isalnum(*p) && !strchr(" .;=/-", *p))
753                         return failure(400, 0, "Invalid characters in mimetype");
754 
755         args = fields[3] ? parse_command(fields[3]) : NULL;
756 
757         if (!args)
758                 return failure(400, 0, "Invalid command parameter");
759 
760         /* First check if we find an ACL match for the whole cmdline ... */
761         allowed = session_access(fields[1], "file", args[0], "exec");
762 
763         /* Now split the command vector... */
764         for (i = 1; args[i]; i++)
765                 args[i][-1] = 0;
766 
767         /* Find executable... */
768         exe = lookup_executable(args[0]);
769 
770         if (!exe) {
771                 free(args);
772                 return failure(404, 0, "Executable not found");
773         }
774 
775         /* If there was no ACL match, check for a match on the executable */
776         if (!allowed && !session_access(fields[1], "file", exe, "exec")) {
777                 free(args);
778                 return failure(403, 0, "Access to command denied by ACL");
779         }
780 
781         if (pipe(fds)) {
782                 free(args);
783                 return failure(500, errno, "Failed to spawn pipe");
784         }
785 
786         switch ((pid = fork()))
787         {
788         case -1:
789                 free(args);
790                 close(fds[0]);
791                 close(fds[1]);
792                 return failure(500, errno, "Failed to fork process");
793 
794         case 0:
795                 devnull = open("/dev/null", O_RDWR);
796 
797                 if (devnull > -1) {
798                         dup2(devnull, 0);
799                         dup2(devnull, 2);
800                         close(devnull);
801                 }
802                 else {
803                         close(0);
804                         close(2);
805                 }
806 
807                 dup2(fds[1], 1);
808                 close(fds[0]);
809                 close(fds[1]);
810 
811                 if (chdir("/") < 0) {
812                         free(args);
813                         return failure(500, errno, "Failed chdir('/')");
814                 }
815 
816                 if (execv(exe, args) < 0) {
817                         free(args);
818                         return failure(500, errno, "Failed execv(...)");
819                 }
820 
821                 return -1;
822 
823         default:
824                 close(fds[1]);
825 
826                 printf("Status: 200 OK\r\n");
827                 printf("Content-Type: %s\r\n",
828                        fields[7] ? fields[7] : "application/octet-stream");
829 
830                 if (fields[5])
831                         printf("Content-Disposition: attachment; filename=\"%s\"\r\n",
832                                fields[5]);
833 
834                 printf("\r\n");
835                 fflush(stdout);
836 
837                 do {
838                         len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
839                 } while (len > 0 || (len == -1 && errno == EINTR));
840 
841                 waitpid(pid, &status, 0);
842 
843                 close(fds[0]);
844                 free(args);
845 
846                 return 0;
847         }
848 }
849 
850 int main(int argc, char **argv)
851 {
852         if (strstr(argv[0], "cgi-upload"))
853                 return main_upload(argc, argv);
854         else if (strstr(argv[0], "cgi-download"))
855                 return main_download(argc, argv);
856         else if (strstr(argv[0], "cgi-backup"))
857                 return main_backup(argc, argv);
858         else if (strstr(argv[0], "cgi-exec"))
859                 return main_exec(argc, argv);
860 
861         return -1;
862 }
863 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt