1 /*
2  * Copyright 2009-2019 Emmanuel Engelhart <kelson@kiwix.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU  General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17  * MA 02110-1301, USA.
18  */
19 
20 #include <getopt.h>
21 #include <kiwix/manager.h>
22 #include <kiwix/server.h>
23 #include <kiwix/name_mapper.h>
24 #include <kiwix/tools/otherTools.h>
25 
26 #ifdef _WIN32
27 # include <windows.h>
28 #else
29 # include <unistd.h>
30 #endif
31 
32 #ifdef __APPLE__
33 # import <sys/sysctl.h>
34 # import <sys/types.h>
35 # define MIBSIZE 4
36 #endif
37 
38 #include "../version.h"
39 
40 #define DEFAULT_THREADS 4
41 
usage()42 void usage()
43 {
44   std::cout << "Usage:" << std::endl
45             << "\tkiwix-serve [OPTIONS] ZIM_PATH+" << std::endl
46             << "\tkiwix-serve --library [OPTIONS] LIBRARY_PATH" << std::endl
47             << std::endl
48 
49             << "Purpose:" << std::endl
50             << "\tDeliver ZIM file articles via HTTP"
51             << std::endl << std::endl
52 
53             << "Mandatory arguments:" << std::endl
54             << "\tLIBRARY_PATH\t\tis the XML library file path listing ZIM file to serve. To be used only with the --library argument."
55             << std::endl
56             << "\tZIM_PATH\t\tis the path of a ZIM file."
57             << std::endl << std::endl
58 
59             << "Optional arguments:" << std::endl
60             << "\t-a, --attachToProcess\texit if given process id is not running anymore" << std::endl
61             << "\t-d, --daemon\t\tdetach the HTTP server daemon from the main process" << std::endl
62             << "\t-i, --address\t\tlisten only on this ip address, all available ones otherwise" << std::endl
63             << "\t-m, --nolibrarybutton\tdo not print the builtin home button in the builtin top bar overlay" << std::endl
64             << "\t-n, --nosearchbar\tdo not print the builtin bar overlay on the top of each served page" << std::endl
65             << "\t-b, --blockexternal\tprevent users from directly accessing external links" << std::endl
66             << "\t-p, --port\t\tTCP port on which to listen to HTTP requests (default: 80)" << std::endl
67             << "\t-r, --urlRootLocation\tURL prefix on which the content should be made available (default: /)" << std::endl
68             << "\t-t, --threads\t\tnumber of threads to run in parallel (default: " << DEFAULT_THREADS << ")" << std::endl
69             << "\t-v, --verbose\t\tprint debug log to STDOUT" << std::endl
70             << "\t-V, --version\t\tprint software version" << std::endl
71             << "\t-z, --nodatealiases\tcreate URL aliases for each content by removing the date" << std::endl
72             << "\t--donottrustlibrary\tRead the metadata from the zim file instead of trusting the library." << std::endl
73             << std::endl
74 
75             << "Documentation:" << std::endl
76             << "\tSource code\t\thttps://github.com/kiwix/kiwix-tools" << std::endl
77             << "\tMore info\t\thttps://wiki.kiwix.org/wiki/Kiwix-serve" << std::endl
78             << std::endl;
79 }
80 
main(int argc,char ** argv)81 int main(int argc, char** argv)
82 {
83   std::string rootLocation = "";
84   kiwix::Library library;
85   unsigned int nb_threads = DEFAULT_THREADS;
86   vector<string> zimPathes;
87   string libraryPath;
88   string rootPath;
89   string address;
90   int serverPort = 80;
91   int daemonFlag [[gnu::unused]] = false;
92   int libraryFlag = false;
93   bool noLibraryButtonFlag = false;
94   bool noSearchBarFlag = false;
95   bool noDateAliasesFlag = false;
96   bool blockExternalLinks = false;
97   bool isVerboseFlag = false;
98   bool trustlibrary = true;
99   string PPIDString;
100   unsigned int PPID = 0;
101 
102   static struct option long_options[]
103       = {{"daemon", no_argument, 0, 'd'},
104          {"verbose", no_argument, 0, 'v'},
105          {"version", no_argument, 0, 'V'},
106          {"library", no_argument, 0, 'l'},
107          {"nolibrarybutton", no_argument, 0, 'm'},
108          {"nodatealiases", no_argument, 0, 'z'},
109          {"nosearchbar", no_argument, 0, 'n'},
110          {"blockexternallinks", no_argument, 0, 'b'},
111          {"attachToProcess", required_argument, 0, 'a'},
112          {"port", required_argument, 0, 'p'},
113          {"address", required_argument, 0, 'i'},
114          {"threads", required_argument, 0, 't'},
115          {"urlRootLocation", required_argument, 0, 'r'},
116          {"donottrustlibrary", no_argument, 0, 'T'},
117          {0, 0, 0, 0}};
118 
119   /* Argument parsing */
120   while (true) {
121     int option_index = 0;
122     int c
123         = getopt_long(argc, argv, "zmnbdvVla:p:f:t:r:i:", long_options, &option_index);
124 
125     if (c != -1) {
126       switch (c) {
127         case 'd':
128           daemonFlag = true;
129           break;
130         case 'v':
131           isVerboseFlag = true;
132           break;
133         case 'V':
134           version();
135           return 0;
136         case 'l':
137           libraryFlag = true;
138           break;
139         case 'n':
140           noSearchBarFlag = true;
141           break;
142         case 'b':
143           blockExternalLinks = true;
144           break;
145         case 'z':
146           noDateAliasesFlag = true;
147           break;
148         case 'm':
149           noLibraryButtonFlag = true;
150           break;
151         case 'T':
152           trustlibrary = false;
153         case 'p':
154           serverPort = atoi(optarg);
155           break;
156         case 'a':
157           PPIDString = string(optarg);
158           PPID = atoi(optarg);
159           break;
160         case 'i':
161           address = string(optarg);
162           break;
163         case 't':
164           nb_threads = atoi(optarg);
165           break;
166         case 'r':
167           rootLocation = string(optarg);
168           break;
169       }
170     } else {
171       if (optind < argc) {
172         if (libraryFlag) {
173           libraryPath = argv[optind++];
174         } else {
175           while (optind < argc)
176             zimPathes.push_back(std::string(argv[optind++]));
177         }
178       }
179       break;
180     }
181   }
182 
183   /* Print usage)) if necessary */
184   if (zimPathes.empty() && libraryPath.empty()) {
185     usage();
186     exit(1);
187   }
188 
189   /* Setup the library manager and get the list of books */
190   kiwix::Manager manager(&library);
191   if (libraryFlag) {
192     vector<string> libraryPaths = kiwix::split(libraryPath, ";");
193     vector<string>::iterator itr;
194 
195     for (itr = libraryPaths.begin(); itr != libraryPaths.end(); ++itr) {
196       if (!itr->empty()) {
197         bool retVal = false;
198 
199         try {
200           string libraryPath
201               = isRelativePath(*itr)
202                     ? computeAbsolutePath(getCurrentDirectory(), *itr)
203                     : *itr;
204           retVal = manager.readFile(libraryPath, true, trustlibrary);
205         } catch (...) {
206           retVal = false;
207         }
208 
209         if (!retVal) {
210           cerr << "Unable to open the XML library file '" << *itr << "'."
211                << endl;
212           exit(1);
213         }
214       }
215     }
216 
217     /* Check if the library is not empty (or only remote books)*/
218     if (library.getBookCount(true, false) == 0) {
219       cerr << "The XML library file '" << libraryPath
220            << "' is empty (or has only remote books)." << endl;
221     }
222   } else {
223     std::vector<std::string>::iterator it;
224     for (it = zimPathes.begin(); it != zimPathes.end(); it++) {
225       if (!manager.addBookFromPath(*it, *it, "", false)) {
226         cerr << "Unable to add the ZIM file '" << *it
227              << "' to the internal library." << endl;
228         exit(1);
229       }
230     }
231   }
232 
233 #ifndef _WIN32
234   /* Fork if necessary */
235   if (daemonFlag) {
236     pid_t pid;
237 
238     /* Fork off the parent process */
239     pid = fork();
240     if (pid < 0) {
241       exit(1);
242     }
243 
244     /* If we got a good PID, then
245        we can exit the parent process. */
246     if (pid > 0) {
247       exit(0);
248     }
249   }
250 #endif
251 
252   kiwix::HumanReadableNameMapper nameMapper(library, noDateAliasesFlag);
253   kiwix::Server server(&library, &nameMapper);
254   server.setAddress(address);
255   server.setRoot(rootLocation);
256   server.setPort(serverPort);
257   server.setNbThreads(nb_threads);
258   server.setVerbose(isVerboseFlag);
259   server.setTaskbar(!noSearchBarFlag, !noLibraryButtonFlag);
260   server.setBlockExternalLinks(blockExternalLinks);
261 
262   if (! server.start()) {
263     cerr << "Unable to instantiate the HTTP daemon. The port " << serverPort
264          << " is maybe already occupied or need more permissions to be open. "
265             "Please try as root or with a port number higher or equal to 1024."
266          << endl;
267     exit(1);
268   }
269 
270   /* Run endless (until PPID dies) */
271   bool waiting = true;
272   do {
273     if (PPID > 0) {
274 #ifdef _WIN32
275       HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, PPID);
276       DWORD ret = WaitForSingleObject(process, 0);
277       CloseHandle(process);
278       if (ret == WAIT_TIMEOUT) {
279 #elif __APPLE__
280       int mib[MIBSIZE];
281       struct kinfo_proc kp;
282       size_t len = sizeof(kp);
283 
284       mib[0] = CTL_KERN;
285       mib[1] = KERN_PROC;
286       mib[2] = KERN_PROC_PID;
287       mib[3] = PPID;
288 
289       int ret = sysctl(mib, MIBSIZE, &kp, &len, NULL, 0);
290       if (ret != -1 && len > 0) {
291 #else /* Linux & co */
292       string procPath = "/proc/" + string(PPIDString);
293       if (access(procPath.c_str(), F_OK) != -1) {
294 #endif
295       } else {
296         waiting = false;
297       }
298     }
299 
300     kiwix::sleep(1000);
301   } while (waiting);
302 
303   /* Stop the daemon */
304   server.stop();
305 }
306