ksgi
/
tutorial0.xml
263 строки · 10.7 Кб
1<article data-sblg-article="1" data-sblg-tags="tutorial" itemscope="itemscope" itemtype="http://schema.org/BlogPosting">2<header>3<h2 itemprop="name">4Getting Started with CGI in C
5</h2>6<address itemprop="author"><a href="https://kristaps.bsd.lv">Kristaps Dzonsons</a></address>7<time itemprop="datePublished" datetime="2015-06-21">21 June, 2015</time>8</header>9<p>10<aside itemprop="about">11This tutorial describes a typical CGI example using <span class="nm">kcgi</span>.12In it, I'll process HTML form input with two named fields, <code>string</code> (a non-empty, unbounded string) and13<code>integer</code>, a signed 64-bit integer.14I'll then output the input within a simple HTML page.
15The tutorial will be laid out in code snippets, which I'll put together at the end.
16I'll then follow with compilation instructions.
17</aside>18</p>19<h3>20Source Code
21</h3>22<p>23I'll describe this as if reading a source file from top to bottom.
24To wit, let's start with the header files.
25We'll obviously need <span class="nm">kcgi</span> and <span class="file">stdint.h</span>, which is necessary for some types26found in the header file.
27I'll also include the HTML library for <span class="nm">kcgi</span>—I'll explain why later.28</p>29<figure class="sample">30<pre class="prettyprint linenums">#include <sys/types.h> /* size_t, ssize_t */31#include <stdarg.h> /* va_list */32#include <stddef.h> /* NULL */33#include <stdint.h> /* int64_t */34#include <kcgi.h>35#include <kcgihtml.h></pre>36</figure>37<p>38Next, I'll assign the fields we're interested in to numeric identifiers.
39This will allow us later to assign names, then assign validators to named fields.
40</p>41<figure class="sample">42<pre class="prettyprint linenums">enum key {43KEY_STRING,
44KEY_INTEGER,
45KEY__MAX
46};</pre>47</figure>48<p>49The enumeration will allow us to bound an array to <code>KEY__MAX</code> and refer to individual buckets in the array by the50enumeration value.
51I'll assume that <code>KEY_STRING</code> is assigned 0 and <code>KEY_INTEGER</code>, 1.52</p>53<p>54Next, connect the indices with validation functions and names.
55The validation function is run by <a href="khttp_parse.3.html">khttp_parse(3)</a>; the name is the HTML form name for the given56element.
57Built-in validation functions, which we'll use, are described in <a href="kvalid_string.3.html">kvalid_string(3)</a>.58In this example, <code>kvalid_stringne</code> will validate a non-empty (nil-terminated) C string, while <code>kvalid_int</code>59will validate a signed 64-bit integer.
60</p>61<figure class="sample">62<pre class="prettyprint linenums">static const struct kvalid keys[KEY__MAX] = {63{ kvalid_stringne, "string" }, /* KEY_STRING */
64{ kvalid_int, "integer" }, /* KEY_INTEGER */
65};</pre>66</figure>67<p>68Next, I define a function that acts upon the parsed fields.
69According to <a href="khttp_parse.3.html">khttp_parse(3)</a>, if a valid value is found, it is assigned into the70<code>fieldmap</code> array.71If one was found but did not validate, it is assigned into the <code>fieldnmap</code> array.72Both of these are indexed by the array position in <code>keys</code>.73(We could also have run the <code>fields</code> list, but that's for chumps.)74</p>75<p>76In this trivial example, the function emits the string values if found or indicates that they're not
77found (or not valid).
78</p>79<figure class="sample">80<pre class="prettyprint linenums">static void process(struct kreq *r) {81struct kpair *p;
82khttp_puts(r, "<p>\n");83khttp_puts(r, "The string value is ");
84if ((p = r->fieldmap[KEY_STRING]))
85khttp_puts(r, p->parsed.s);
86else if (r->fieldnmap[KEY_STRING])
87khttp_puts(r, "<i>failed parse</i>");88else
89khttp_puts(r, "<i>not provided</i>");90khttp_puts(r, "</p>\n");91}</pre>92</figure>93<p>94As is, this routine introduces a significant problem: if the <code>KEY_STRING</code> value consists of HTML, it will be inserted95directly into the stream, allowing attackers to use <a href="https://en.wikipedia.org/wiki/Cross-site_scripting">XSS</a>.96Instead, let's use the <a href="kcgihtml.3.html">kcgihtml(3)</a> library to perform the proper encoding and element nesting.97</p>98<figure class="sample">99<pre class="prettyprint linenums">static void process_safe(struct kreq *r) {100struct kpair *p;
101struct khtmlreq req;
102khtml_open(&req, r, 0);103khtml_elem(&req, KELEM_P);104khtml_puts(&req, "The string value is ");105if ((p = r->fieldmap[KEY_STRING])) {
106khtml_puts(&req, p->parsed.s);107} else if (r->fieldnmap[KEY_STRING]) {
108khtml_elem(&req, KELEM_I);109khtml_puts(&req, "failed parse");110} else {
111khtml_elem(&req, KELEM_I);112khtml_puts(&req, "not provided");113}
114khtml_close(&req);115}</pre>116</figure>117<p>118Before doing any parsing, I sanitise the HTTP context.
119This consists of the page requested, MIME type, HTTP method, and so on.
120</p>121<p>122To begin, I provide an array of indexed page identifiers—similarly as I did for the field validator and name.123This will also be passed to <a href="khttp_parse.3.html">khttp_parse(3)</a>.124These define the page requests accepted by the application, in this case being only <code>index</code>, which I'll also set to125be the default page when invoked without a path (i.e., just <code>http://www.foo.com</code>).126<strong>Note</strong>: this is the first path component, so specifying <code>index</code> will also accept127<code>index/foo</code>.128</p>129<figure class="sample">130<pre class="prettyprint linenums">enum page {131PAGE_INDEX,
132PAGE__MAX
133};
134const char *const pages[PAGE__MAX] = {
135"index", /* PAGE_INDEX */
136};</pre>137</figure>138<p>139Now, I validate the page request and HTTP context based upon the defined components.
140This function checks the page request (it must be <code>index</code> without a subpath), HTML MIME type (expanding to141<code>index.html</code>), and HTTP method (it must be an HTTP <code>GET</code>, such as <code>index.html?string=foo</code>).142To keep things reasonable, I'll have the sanitiser return an HTTP error code (see <a143href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">RFC 2616</a> for an explanation).144</p>145<figure class="sample">146<pre class="prettyprint linenums">static enum khttp sanitise(const struct kreq *r) {147if (r->page != PAGE_INDEX)
148return KHTTP_404;
149else if (*r->path != '\0') /* no index/xxxx */
150return KHTTP_404;
151else if (r->mime != KMIME_TEXT_HTML)
152return KHTTP_404;
153else if (r->method != KMETHOD_GET)
154return KHTTP_405;
155return KHTTP_200;
156}</pre>157</figure>158<p>159Putting all of these together: parse the HTTP context, validate it, process it, then free the resources.
160Headers are output using <a href="khttp_head.3.html">khttp_head(3)</a>, with the document body started161with <a href="khttp_body.3.html">khttp_body(3)</a>.162The HTTP context is closed with <a href="khttp_free.3.html">khttp_free(3)</a>.163</p>164<figure class="sample">165<pre class="prettyprint linenums">int main(void) {166struct kreq r;
167enum khttp er;
168if (khttp_parse(&r, keys, KEY__MAX,169pages, PAGE__MAX, PAGE_INDEX) != KCGI_OK)
170return 0;
171if ((er = sanitise(&r)) != KHTTP_200) {172khttp_head(&r, kresps[KRESP_STATUS],173"%s", khttps[er]);
174khttp_head(&r, kresps[KRESP_CONTENT_TYPE],175"%s", kmimetypes[KMIME_TEXT_PLAIN]);
176khttp_body(&r);177if (KMIME_TEXT_HTML == r.mime)
178khttp_puts(&r, "Could not service request.");179} else {
180khttp_head(&r, kresps[KRESP_STATUS],181"%s", khttps[KHTTP_200]);
182khttp_head(&r, kresps[KRESP_CONTENT_TYPE],183"%s", kmimetypes[r.mime]);
184khttp_body(&r);185process_safe(&r);186}
187khttp_free(&r);188return 0;
189};</pre>190</figure>191<p>192That's it!
193</p>194<h3>195Compile and Link
196</h3>197<p>198Your source is no good til it's compiled and linked into an executable.
199In this section I'll mention two strategies: the first is where the application is dynamically linked; in the second,
200statically.
201Dynamic linking is normal for most applications, but CGI applications are often placed in a file-system jail (a chroot(2))
202without access to other libraries, and are thus statically linked.
203In short, it depends on your environment.
204Let's call our application <code>tutorial0.cgi</code> and the source file, <code>tutorial0.c</code>.205To dynamically link:
206</p>207<figure class="sample">208<pre class="prettyprint lang-sh linenums">% cc `pkg-config --cflags kcgi-html` -c -o tutorial0.o tutorial0.c209% cc -o tutorial0.cgi tutorial0.o `pkg-config --libs kcgi-html`</pre>210</figure>211<p>212For static linking, which is the norm in more sophisticated systems like OpenBSD:
213</p>214<figure class="sample">215<pre class="prettyprint lang-sh linenums">% cc -static -o tutorial0.cgi tutorial0.o `pkg-config --libs kcgi-html`</pre>216</figure>217<h3>218Install
219</h3>220<p>221Installation steps depends on your operating system, web server, and a thousand other factors.
222I'll stick with the simplest installation using the defaults of <a href="https://www.openbsd.org">OpenBSD</a> with the default223web server <a href="https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man8/httpd.8">httpd(8)</a>.224To begin with, configure <span class="file">/etc/httpd.conf</span> with your server's root being in <span225class="file">/var/www</span> and FastCGI being in <span class="file">/var/www/cgi-bin</span>.226If you've already done this, or have a configuration file in place, you won't need to do this.
227</p>228<figure class="sample">229<pre class="prettyprint lang-sh linenums">server "me.local" {230listen on * port 80
231root "/htdocs"
232location "/cgi-bin/*" {
233fastcgi
234root "/"
235}
236}</pre>237</figure>238<p>239Next, we use the <a href="https://man.openbsd.org/rcctl.8">rcctl(8)</a> tool to enable and240start the <a href="https://man.openbsd.org/httpd.8">httpd(8)</a> webserver and241<a href="https://man.openbsd.org/slowcgi.8">slowcgi(8)</a> wrapper.242(The latter is necessary because <a href="https://man.openbsd.org/httpd.8">httpd(8)</a> only243directly supports FastCGI, so a proxy is necessary.)
244Again, you may not need to do this part.
245We also make sure the instructions on the main page are followed regarding OpenBSD sandboxing in the file-system jail.
246</p>247<figure class="sample">248<pre class="prettyprint lang-sh linenums">% doas rcctl enable httpd249% doas rcctl start httpd
250% doas rcctl check httpd
251httpd(ok)
252% doas rcctl enable slowcgi
253% doas rcctl start slowcgi
254% doas rcctl check slowcgi
255slowcgi(ok)</pre>256</figure>257<p>258Assuming we built the static binary, we can now just install into the CGI directory and be ready to go!
259</p>260<figure class="sample">261<pre class="prettyprint lang-sh linenums">% doas install -m 0555 tutorial0.cgi /var/www/cgi-bin</pre>262</figure>263</article>264