ksgi

Форк
0
/
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">
4
			Getting 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">
11
			This tutorial describes a typical CGI example using <span class="nm">kcgi</span>.
12
			In it, I'll process HTML form input with two named fields, <code>string</code> (a non-empty, unbounded string) and
13
			<code>integer</code>, a signed 64-bit integer.
14
			I'll then output the input within a simple HTML page.
15
			The tutorial will be laid out in code snippets, which I'll put together at the end.
16
			I'll then follow with compilation instructions.
17
		</aside>
18
	</p>
19
	<h3>
20
		Source Code
21
	</h3>
22
	<p>
23
		I'll describe this as if reading a source file from top to bottom.
24
		To wit, let's start with the header files.
25
		We'll obviously need <span class="nm">kcgi</span> and <span class="file">stdint.h</span>, which is necessary for some types
26
		found in the header file.
27
		I'll also include the HTML library for <span class="nm">kcgi</span>&#8212;I'll explain why later.
28
	</p>
29
	<figure class="sample">
30
		<pre class="prettyprint linenums">#include &lt;sys/types.h&gt; /* size_t, ssize_t */
31
#include &lt;stdarg.h&gt; /* va_list */
32
#include &lt;stddef.h&gt; /* NULL */
33
#include &lt;stdint.h&gt; /* int64_t */
34
#include &lt;kcgi.h&gt;
35
#include &lt;kcgihtml.h&gt;</pre>
36
	</figure>
37
	<p>
38
		Next, I'll assign the fields we're interested in to numeric identifiers.
39
		This 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 {
43
  KEY_STRING,
44
  KEY_INTEGER,
45
  KEY__MAX
46
};</pre>
47
	</figure>
48
	<p>
49
		The enumeration will allow us to bound an array to <code>KEY__MAX</code> and refer to individual buckets in the array by the
50
		enumeration value.
51
		I'll assume that <code>KEY_STRING</code> is assigned 0 and <code>KEY_INTEGER</code>, 1.
52
	</p>
53
	<p>
54
		Next, connect the indices with validation functions and names.
55
		The validation function is run by <a href="khttp_parse.3.html">khttp_parse(3)</a>; the name is the HTML form name for the given
56
		element.
57
		Built-in validation functions, which we'll use, are described in <a href="kvalid_string.3.html">kvalid_string(3)</a>.
58
		In this example, <code>kvalid_stringne</code> will validate a non-empty (nil-terminated) C string, while <code>kvalid_int</code>
59
		will 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>
68
		Next, I define a function that acts upon the parsed fields.
69
		According to <a href="khttp_parse.3.html">khttp_parse(3)</a>, if a valid value is found, it is assigned into the
70
		<code>fieldmap</code> array.
71
		If one was found but did not validate, it is assigned into the <code>fieldnmap</code> array.
72
		Both 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>
76
		In this trivial example, the function emits the string values if found or indicates that they're not
77
		found (or not valid).
78
	</p>
79
	<figure class="sample">
80
		<pre class="prettyprint linenums">static void process(struct kreq *r) {
81
  struct kpair *p;
82
  khttp_puts(r, "&lt;p&gt;\n");
83
  khttp_puts(r, "The string value is ");
84
  if ((p = r->fieldmap[KEY_STRING]))
85
    khttp_puts(r, p->parsed.s);
86
  else if (r->fieldnmap[KEY_STRING])
87
    khttp_puts(r, "&lt;i&gt;failed parse&lt;/i&gt;");
88
  else 
89
    khttp_puts(r, "&lt;i&gt;not provided&lt;/i&gt;");
90
  khttp_puts(r, "&lt;/p&gt;\n");
91
}</pre>
92
	</figure>
93
	<p>
94
		As is, this routine introduces a significant problem: if the <code>KEY_STRING</code> value consists of HTML, it will be inserted
95
		directly into the stream, allowing attackers to use <a href="https://en.wikipedia.org/wiki/Cross-site_scripting">XSS</a>.
96
		Instead, 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) {
100
  struct kpair *p;
101
  struct khtmlreq req;
102
  khtml_open(&amp;req, r, 0);
103
  khtml_elem(&amp;req, KELEM_P);
104
  khtml_puts(&amp;req, "The string value is ");
105
  if ((p = r->fieldmap[KEY_STRING])) {
106
    khtml_puts(&amp;req, p->parsed.s);
107
  } else if (r->fieldnmap[KEY_STRING]) {
108
    khtml_elem(&amp;req, KELEM_I);
109
    khtml_puts(&amp;req, "failed parse");
110
  } else {
111
    khtml_elem(&amp;req, KELEM_I);
112
    khtml_puts(&amp;req, "not provided");
113
  }
114
  khtml_close(&amp;req);
115
}</pre>
116
	</figure>
117
	<p>
118
		Before doing any parsing, I sanitise the HTTP context.
119
		This consists of the page requested, MIME type, HTTP method, and so on.
120
	</p>
121
	<p>
122
		To begin, I provide an array of indexed page identifiers&#8212;similarly as I did for the field validator and name.
123
		This will also be passed to <a href="khttp_parse.3.html">khttp_parse(3)</a>.
124
		These define the page requests accepted by the application, in this case being only <code>index</code>, which I'll also set to
125
		be 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 accept
127
		<code>index/foo</code>.
128
	</p>
129
	<figure class="sample">
130
		<pre class="prettyprint linenums">enum page {
131
  PAGE_INDEX,
132
  PAGE__MAX
133
};
134
const char *const pages[PAGE__MAX] = {
135
  "index", /* PAGE_INDEX */
136
};</pre>
137
	</figure>
138
	<p>
139
		Now, I validate the page request and HTTP context based upon the defined components.
140
		This function checks the page request (it must be <code>index</code> without a subpath), HTML MIME type (expanding to
141
		<code>index.html</code>), and HTTP method (it must be an HTTP <code>GET</code>, such as <code>index.html?string=foo</code>).
142
		To keep things reasonable, I'll have the sanitiser return an HTTP error code (see <a
143
			href="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) {
147
  if (r->page != PAGE_INDEX)
148
    return KHTTP_404;
149
  else if (*r->path != '\0') /* no index/xxxx */
150
    return KHTTP_404;
151
  else if (r->mime != KMIME_TEXT_HTML)
152
    return KHTTP_404;
153
  else if (r->method != KMETHOD_GET)
154
    return KHTTP_405;
155
  return KHTTP_200;
156
}</pre> 
157
	</figure>
158
	<p>
159
		Putting all of these together: parse the HTTP context, validate it, process it, then free the resources.
160
		Headers are output using <a href="khttp_head.3.html">khttp_head(3)</a>, with the document body started
161
		with <a href="khttp_body.3.html">khttp_body(3)</a>.
162
		The 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) {
166
  struct kreq r;
167
  enum khttp er;
168
  if (khttp_parse(&amp;r, keys, KEY__MAX,
169
      pages, PAGE__MAX, PAGE_INDEX) != KCGI_OK)
170
    return 0;
171
  if ((er = sanitise(&amp;r)) != KHTTP_200) {
172
    khttp_head(&amp;r, kresps[KRESP_STATUS],
173
      "%s", khttps[er]);
174
    khttp_head(&amp;r, kresps[KRESP_CONTENT_TYPE],
175
      "%s", kmimetypes[KMIME_TEXT_PLAIN]);
176
    khttp_body(&amp;r);
177
    if (KMIME_TEXT_HTML == r.mime)
178
      khttp_puts(&amp;r, "Could not service request.");
179
  } else {
180
    khttp_head(&amp;r, kresps[KRESP_STATUS],
181
      "%s", khttps[KHTTP_200]);
182
    khttp_head(&amp;r, kresps[KRESP_CONTENT_TYPE],
183
      "%s", kmimetypes[r.mime]);
184
    khttp_body(&amp;r);
185
    process_safe(&amp;r);
186
  }
187
  khttp_free(&amp;r);
188
  return 0;
189
};</pre>
190
	</figure>
191
	<p>
192
		That's it!
193
	</p>
194
	<h3>
195
		Compile and Link
196
	</h3>
197
	<p>
198
		Your source is no good til it's compiled and linked into an executable.
199
		In this section I'll mention two strategies: the first is where the application is dynamically linked; in the second,
200
		statically.
201
		Dynamic linking is normal for most applications, but CGI applications are often placed in a file-system jail (a chroot(2))
202
		without access to other libraries, and are thus statically linked.
203
		In short, it depends on your environment.
204
		Let's call our application <code>tutorial0.cgi</code> and the source file, <code>tutorial0.c</code>.
205
		To 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.c
209
% cc -o tutorial0.cgi tutorial0.o `pkg-config --libs kcgi-html`</pre>
210
	</figure>
211
	<p>
212
		For 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>
218
		Install
219
	</h3>
220
	<p>
221
		Installation steps depends on your operating system, web server, and a thousand other factors.
222
		I'll stick with the simplest installation using the defaults of <a href="https://www.openbsd.org">OpenBSD</a> with the default
223
		web server <a href="https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man8/httpd.8">httpd(8)</a>.
224
		To begin with, configure <span class="file">/etc/httpd.conf</span> with your server's root being in <span
225
			class="file">/var/www</span> and FastCGI being in <span class="file">/var/www/cgi-bin</span>.
226
		If 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" {
230
  listen on * port 80
231
  root "/htdocs"
232
  location "/cgi-bin/*" {
233
    fastcgi
234
    root "/"
235
  }
236
}</pre>
237
	</figure>
238
	<p>
239
		Next, we use the <a href="https://man.openbsd.org/rcctl.8">rcctl(8)</a> tool to enable and
240
		start the <a href="https://man.openbsd.org/httpd.8">httpd(8)</a> webserver and
241
		<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> only
243
		directly supports FastCGI, so a proxy is necessary.)
244
		Again, you may not need to do this part.
245
		We 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 httpd
249
% doas rcctl start httpd
250
% doas rcctl check httpd
251
httpd(ok)
252
% doas rcctl enable slowcgi
253
% doas rcctl start slowcgi
254
% doas rcctl check slowcgi
255
slowcgi(ok)</pre>
256
	</figure>
257
	<p>
258
		Assuming 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

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.