Bug 841, CVE-2006-5925: Prevent enabling the SMB protocol.
[elinks.git] / src / protocol / smb / smb.c
blobe183baed80c632fe64b449d52c8f395aad3ddf33
1 /* Internal SMB protocol implementation */
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE /* Needed for asprintf() */
5 #endif
7 #error SMB protocol support is vulnerable to CVE-2006-5925. Do not use.
8 #error If you want to use SMB, please vote for bug 844 or post a patch.
10 #ifdef HAVE_CONFIG_H
11 #include "config.h"
12 #endif
14 #include <errno.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/types.h>
19 #ifdef HAVE_FCNTL_H
20 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
21 #endif
22 #ifdef HAVE_UNISTD_H
23 #include <unistd.h>
24 #endif
26 #include "elinks.h"
28 #include "cache/cache.h"
29 #include "config/options.h"
30 #include "intl/gettext/libintl.h"
31 #include "main/module.h"
32 #include "main/select.h"
33 #include "network/connection.h"
34 #include "network/socket.h"
35 #include "osdep/osdep.h"
36 #include "protocol/common.h"
37 #include "protocol/protocol.h"
38 #include "protocol/smb/smb.h"
39 #include "protocol/uri.h"
40 #include "util/memory.h"
41 #include "util/snprintf.h"
42 #include "util/string.h"
44 /* XXX: Nice cleanup target --pasky */
45 /* FIXME: we rely on smbclient output which may change in future,
46 * so i think we should use libsmbclient instead (or better in addition)
47 * This stuff is a quick hack, but it works ;). --Zas */
49 enum smb_list_type {
50 SMB_LIST_NONE,
51 SMB_LIST_SHARES,
52 SMB_LIST_DIR,
55 struct smb_connection_info {
56 enum smb_list_type list_type;
58 /* If this is 1, it means one socket is already off. The second one
59 * should call end_smb_connection() when it goes off as well. */
60 int closing;
62 size_t textlen;
63 unsigned char text[1];
66 static void end_smb_connection(struct connection *conn);
69 struct option_info smb_options[] = {
70 INIT_OPT_TREE("protocol", N_("SMB"),
71 "smb", 0,
72 N_("SAMBA specific options.")),
74 INIT_OPT_STRING("protocol.smb", N_("Credentials"),
75 "credentials", 0, "",
76 N_("Credentials file passed to smbclient via -A option.")),
78 NULL_OPTION_INFO,
81 struct module smb_protocol_module = struct_module(
82 /* name: */ N_("SMB"),
83 /* options: */ smb_options,
84 /* hooks: */ NULL,
85 /* submodules: */ NULL,
86 /* data: */ NULL,
87 /* init: */ NULL,
88 /* done: */ NULL
92 /* Return 0 if @conn->cached was set. */
93 static int
94 smb_get_cache(struct connection *conn)
96 if (conn->cached) return 0;
98 conn->cached = get_cache_entry(conn->uri);
99 if (conn->cached) return 0;
101 abort_connection(conn, S_OUT_OF_MEM);
102 return -1;
106 #define READ_SIZE 4096
108 static ssize_t
109 smb_read_data(struct connection *conn, int sock, unsigned char *dst)
111 ssize_t r;
112 struct smb_connection_info *si = conn->info;
114 r = read(sock, dst, READ_SIZE);
115 if (r == -1) {
116 retry_connection(conn, -errno);
117 return -1;
119 if (r == 0) {
120 if (!si->closing) {
121 si->closing = 1;
122 clear_handlers(sock);
123 return 0;
125 end_smb_connection(conn);
126 return 0;
129 return r;
132 static void
133 smb_read_text(struct connection *conn, int sock)
135 ssize_t r;
136 struct smb_connection_info *si = conn->info;
138 /* We add 2 here to handle LF and NUL chars that are added in
139 * smb_end_connection(). */
140 si = mem_realloc(si, sizeof(*si) + si->textlen
141 + READ_SIZE + 2);
142 if (!si) {
143 abort_connection(conn, S_OUT_OF_MEM);
144 return;
146 conn->info = si;
148 r = smb_read_data(conn, sock, si->text + si->textlen);
149 if (r <= 0) return;
151 if (!conn->from) set_connection_state(conn, S_GETH);
152 si->textlen += r;
155 static void
156 smb_got_data(struct connection *conn)
158 struct smb_connection_info *si = conn->info;
159 unsigned char buffer[READ_SIZE];
160 ssize_t r;
162 if (si->list_type != SMB_LIST_NONE) {
163 smb_read_text(conn, conn->data_socket->fd);
164 return;
167 r = smb_read_data(conn, conn->data_socket->fd, buffer);
168 if (r <= 0) return;
170 set_connection_state(conn, S_TRANS);
172 if (smb_get_cache(conn)) return;
174 conn->received += r;
175 if (add_fragment(conn->cached, conn->from, buffer, r) == 1)
176 conn->tries = 0;
177 conn->from += r;
180 #undef READ_SIZE
182 static void
183 smb_got_text(struct connection *conn)
185 smb_read_text(conn, conn->socket->fd);
188 /* Search for @str1 followed by @str2 in @line.
189 * It returns NULL if not found, or pointer to start
190 * of @str2 in @line if found. */
191 static unsigned char *
192 find_strs(unsigned char *line, unsigned char *str1,
193 unsigned char *str2)
195 unsigned char *p = strstr(line, str1);
197 if (!p) return NULL;
199 p = strstr(p + strlen(str1), str2);
200 return p;
203 static void
204 parse_smbclient_output(struct uri *uri, struct smb_connection_info *si,
205 struct string *page)
207 unsigned char *line_start, *line_end;
208 enum {
209 SMB_TYPE_NONE,
210 SMB_TYPE_SHARE,
211 SMB_TYPE_SERVER,
212 SMB_TYPE_WORKGROUP
213 } type = SMB_TYPE_NONE;
214 size_t pos = 0;
215 int stop;
217 assert(uri && si && page);
218 if_assert_failed return;
220 add_to_string(page, "<html><head><title>/");
221 add_bytes_to_string(page, uri->data, uri->datalen);
222 add_to_string(page, "</title></head><body><pre>");
224 line_start = si->text;
225 stop = !si->textlen; /* Nothing to parse. */
226 while (!stop && (line_end = strchr(line_start, ASCII_LF))) {
227 unsigned char *line = line_start;
228 size_t line_len;
229 size_t start_offset = 0;
231 /* Handle \r\n case. Normally, do not occur on *nix. */
232 if (line_end > line_start && line_end[-1] == ASCII_CR)
233 line_end--, start_offset++;
235 line_len = line_end - line_start;
237 /* Here we modify si->text content, this should not be
238 * a problem as it is only used here. This prevents
239 * allocation of memory for the line. */
240 *line_end = '\0';
242 /* And I got bored here with cleaning it up. --pasky */
244 if (si->list_type == SMB_LIST_SHARES) {
245 unsigned char *ll, *lll, *found;
247 if (!*line) type = SMB_TYPE_NONE;
249 found = find_strs(line, "Sharename", "Type");
250 if (found) {
251 pos = found - line;
252 type = SMB_TYPE_SHARE;
253 goto print_as_is;
256 found = find_strs(line, "Server", "Comment");
257 if (found) {
258 type = SMB_TYPE_SERVER;
259 goto print_as_is;
262 found = find_strs(line, "Workgroup", "Master");
263 if (found) {
264 pos = found - line;
265 type = SMB_TYPE_WORKGROUP;
266 goto print_as_is;
269 if (type == SMB_TYPE_NONE)
270 goto print_as_is;
272 for (ll = line; *ll; ll++)
273 if (!isspace(*ll) && *ll != '-')
274 goto print_next;
276 goto print_as_is;
278 print_next:
279 for (ll = line; *ll; ll++)
280 if (!isspace(*ll))
281 break;
283 for (lll = ll; *lll; lll++)
284 if (isspace(*lll))
285 break;
287 switch (type) {
288 case SMB_TYPE_SHARE:
290 unsigned char *llll;
292 if (!strstr(lll, "Disk"))
293 goto print_as_is;
295 if (pos && pos < line_len
296 && isspace(*(llll = line + pos - 1))
297 && llll > ll) {
298 while (llll > ll && isspace(*llll))
299 llll--;
300 if (!isspace(*llll))
301 lll = llll + 1;
304 add_bytes_to_string(page, line, ll - line);
305 add_to_string(page, "<a href=\"");
306 add_bytes_to_string(page, ll, lll - ll);
307 add_to_string(page, "/\">");
308 add_bytes_to_string(page, ll, lll - ll);
309 add_to_string(page, "</a>");
310 add_to_string(page, lll);
311 break;
314 case SMB_TYPE_WORKGROUP:
315 if (pos < line_len && pos
316 && isspace(line[pos - 1])
317 && !isspace(line[pos])) {
318 ll = line + pos;
319 } else {
320 for (ll = lll; *ll; ll++)
321 if (!isspace(*ll))
322 break;
324 for (lll = ll; *lll; lll++)
325 if (isspace(*lll))
326 break;
327 /* Fall-through */
329 case SMB_TYPE_SERVER:
330 add_bytes_to_string(page, line, ll - line);
331 add_to_string(page, "<a href=\"smb://");
332 add_bytes_to_string(page, ll, lll - ll);
333 add_to_string(page, "/\">");
334 add_bytes_to_string(page, ll, lll - ll);
335 add_to_string(page, "</a>");
336 add_to_string(page, lll);
337 break;
339 case SMB_TYPE_NONE:
340 goto print_as_is;
343 } else if (si->list_type == SMB_LIST_DIR) {
344 if (strstr(line, "NT_STATUS")) {
345 /* Error, stop after message. */
346 stop = 1;
347 goto print_as_is;
350 if (line_end - line_start >= 5
351 && line_start[0] == ' '
352 && line_start[1] == ' '
353 && line_start[2] != ' ') {
354 int dir = 0;
355 int may_be_dir = 0;
356 unsigned char *p = line_end;
357 unsigned char *url = line_start + 2;
359 /* smbclient list parser
360 * The boring thing is that output is
361 * ambiguous in many ways:
362 * filenames with more than one space,
363 * etc...
364 * This bloated code tries to do a not
365 * so bad job. --Zas */
367 /* directory D 0 Fri May 7 11:23:18 2004 */
368 /* filename 2444 Thu Feb 19 15:52:46 2004 */
370 /* Skip end of line */
371 while (p > url && !isdigit(*p)) p--;
372 if (p == url) goto print_as_is;
374 /* FIXME: Use parse_date()? */
375 /* year */
376 while (p > url && isdigit(*p)) p--;
377 if (p == url || !isspace(*p)) goto print_as_is;
378 while (p > url && isspace(*p)) p--;
380 /* seconds */
381 while (p > url && isdigit(*p)) p--;
382 if (p == url || *p != ':') goto print_as_is;
383 p--;
385 /* minutes */
386 while (p > url && isdigit(*p)) p--;
387 if (p == url || *p != ':') goto print_as_is;
388 p--;
390 /* hours */
391 while (p > url && isdigit(*p)) p--;
392 if (p == url || !isspace(*p)) goto print_as_is;
393 p--;
395 /* day as number */
396 while (p > url && isdigit(*p)) p--;
397 while (p > url && isspace(*p)) p--;
398 if (p == url) goto print_as_is;
400 /* month */
401 while (p > url && !isspace(*p)) p--;
402 if (p == url || !isspace(*p)) goto print_as_is;
403 p--;
405 /* day name */
406 while (p > url && !isspace(*p)) p--;
407 if (p == url || !isspace(*p)) goto print_as_is;
408 while (p > url && isspace(*p)) p--;
410 /* file size */
411 if (p == url || !isdigit(*p)) goto print_as_is;
413 if (*p == '0' && isspace(*(p - 1))) may_be_dir = 1;
415 while (p > url && isdigit(*p)) p--;
416 if (p == url) goto print_as_is;
418 /* Magic to determine if we have a
419 * filename or a dirname. Thanks to
420 * smbclient ambiguous output. */
422 unsigned char *pp = p;
424 while (pp > url && isspace(*pp)) pp--;
426 if (p - pp <= 8) {
427 while (pp > url
428 && (*pp == 'D'
429 || *pp == 'H'
430 || *pp == 'A'
431 || *pp == 'S'
432 || *pp == 'R'
433 || *pp == 'V')) {
434 if (*pp == 'D' && may_be_dir)
435 dir = 1;
436 pp--;
439 while (pp > url && isspace(*pp)) pp--;
440 p = pp;
443 /* Don't display '.' directory */
444 if (p == url && *url == '.') goto ignored;
445 p++;
447 add_to_string(page, " <a href=\"");
448 add_bytes_to_string(page, url, p - url);
449 if (dir) add_char_to_string(page, '/');
450 add_to_string(page, "\">");
451 add_bytes_to_string(page, url, p - url);
452 add_to_string(page, "</a>");
453 add_bytes_to_string(page, p, line_end - p);
455 } else {
456 goto print_as_is;
459 } else {
460 print_as_is:
461 add_bytes_to_string(page, line_start, line_len);
464 add_char_to_string(page, ASCII_LF);
465 ignored:
466 line_start = line_end + start_offset + 1;
469 add_to_string(page, "</pre></body></html>");
472 static void
473 end_smb_connection(struct connection *conn)
475 struct smb_connection_info *si = conn->info;
476 struct uri *uri;
477 enum connection_state state = S_OK;
479 if (smb_get_cache(conn)) return;
481 if (conn->from)
482 goto bye;
484 /* Ensure termination by LF + NUL chars, memory for this
485 * was reserved by smb_read_text(). */
486 if (si->textlen && si->text[si->textlen - 1] != ASCII_LF)
487 si->text[si->textlen++] = ASCII_LF;
488 si->text[si->textlen] = '\0';
490 uri = conn->uri;
491 if (uri->datalen
492 && uri->data[uri->datalen - 1] != '/'
493 && uri->data[uri->datalen - 1] != '\\'
494 && (strstr(si->text, "NT_STATUS_FILE_IS_A_DIRECTORY")
495 || strstr(si->text, "NT_STATUS_ACCESS_DENIED")
496 || strstr(si->text, "ERRbadfile"))) {
497 redirect_cache(conn->cached, "/", 1, 0);
499 } else {
500 struct string page;
502 if (!init_string(&page)) {
503 state = S_OUT_OF_MEM;
504 goto bye;
507 parse_smbclient_output(uri, si, &page);
509 add_fragment(conn->cached, 0, page.source, page.length);
510 conn->from += page.length;
511 done_string(&page);
513 mem_free_set(&conn->cached->content_type, stracpy("text/html"));
516 bye:
517 close_socket(conn->socket);
518 close_socket(conn->data_socket);
519 abort_connection(conn, state);
523 void
524 smb_protocol_handler(struct connection *conn)
526 int out_pipe[2] = { -1, -1 };
527 int err_pipe[2] = { -1, -1 };
528 unsigned char *share, *dir;
529 unsigned char *p;
530 pid_t cpid;
531 int dirlen;
532 struct smb_connection_info *si;
533 struct uri *uri;
535 si = mem_calloc(1, sizeof(*si) + 2);
536 if (!si) {
537 abort_connection(conn, S_OUT_OF_MEM);
538 return;
540 conn->info = si;
542 uri = conn->uri;
543 p = strchr(uri->data, '/');
544 if (p && p - uri->data < uri->datalen) {
545 share = memacpy(uri->data, p - uri->data);
546 dir = p + 1;
547 /* FIXME: ensure @dir do not contain dangerous chars. --Zas */
549 } else if (uri->datalen) {
550 if (smb_get_cache(conn)) return;
552 redirect_cache(conn->cached, "/", 1, 0);
553 abort_connection(conn, S_OK);
554 return;
556 } else {
557 share = stracpy("");
558 dir = "";
561 if (!share) {
562 abort_connection(conn, S_OUT_OF_MEM);
563 return;
566 dirlen = strlen(dir);
567 if (!*share) {
568 si->list_type = SMB_LIST_SHARES;
569 } else if (!dirlen || dir[dirlen - 1] == '/'
570 || dir[dirlen - 1] == '\\') {
571 si->list_type = SMB_LIST_DIR;
574 if (c_pipe(out_pipe) || c_pipe(err_pipe)) {
575 int s_errno = errno;
577 if (out_pipe[0] >= 0) close(out_pipe[0]);
578 if (out_pipe[1] >= 0) close(out_pipe[1]);
579 mem_free(share);
580 abort_connection(conn, -s_errno);
581 return;
584 conn->from = 0;
586 cpid = fork();
587 if (cpid == -1) {
588 int s_errno = errno;
590 close(out_pipe[0]);
591 close(out_pipe[1]);
592 close(err_pipe[0]);
593 close(err_pipe[1]);
594 mem_free(share);
595 retry_connection(conn, -s_errno);
596 return;
599 if (!cpid) {
600 #define SMBCLIENT "smbclient"
601 #define MAX_SMBCLIENT_ARGS 32
602 int n = 0;
603 unsigned char *v[MAX_SMBCLIENT_ARGS];
604 unsigned char *optstr;
606 close(1);
607 dup2(out_pipe[1], 1);
608 close(2);
609 dup2(err_pipe[1], 2);
610 close(0);
611 dup2(open("/dev/null", O_RDONLY), 0);
613 close_all_non_term_fd();
614 close(out_pipe[0]);
615 close(err_pipe[0]);
617 /* Usage: smbclient service <password> [options] */
618 v[n++] = SMBCLIENT;
620 /* FIXME: handle alloc failures. */
621 /* At this point, we are the child process.
622 * Maybe we just don't care if the child kills itself
623 * dereferencing a NULL pointer... -- Miciah */
624 /* Leaving random core files after itself is not what a nice
625 * program does. Also, the user might also want to know, why
626 * the hell does he see nothing on the screen. --pasky */
628 if (*share) {
629 /* Construct service path. */
630 asprintf((char **) &v[n++], "//%.*s/%s",
631 uri->hostlen, uri->host, share);
633 /* Add password if any. */
634 if (uri->passwordlen && !uri->userlen) {
635 v[n++] = memacpy(uri->password, uri->passwordlen);
637 } else {
638 /* Get a list of shares available on a host. */
639 v[n++] = "-L";
640 v[n++] = memacpy(uri->host, uri->hostlen);
643 v[n++] = "-N"; /* Don't ask for a password. */
644 v[n++] = "-E"; /* Write messages to stderr instead of stdout. */
645 v[n++] = "-d 0"; /* Disable debug mode. */
647 if (uri->portlen) {
648 /* Connect to the specified port. */
649 v[n++] = "-p";
650 v[n++] = memacpy(uri->port, uri->portlen);
653 if (uri->userlen) {
654 /* Set the network username. */
655 v[n++] = "-U";
656 if (!uri->passwordlen) {
657 /* No password. */
658 v[n++] = memacpy(uri->user, uri->userlen);
659 } else {
660 /* With password. */
661 asprintf((char **) &v[n++], "%.*s%%%.*s",
662 uri->userlen, uri->user,
663 uri->passwordlen, uri->password);
667 if (*share) {
668 /* FIXME: use si->list_type here ?? --Zas */
669 if (!dirlen || dir[dirlen - 1] == '/' || dir[dirlen - 1] == '\\') {
670 if (dirlen) {
671 /* Initial directory. */
672 v[n++] = "-D";
673 v[n++] = dir;
676 v[n++] = "-c"; /* Execute semicolon separated commands. */
677 v[n++] = "ls"; /* List files. */
679 } else {
680 /* Copy remote file to stdout. */
681 unsigned char *s = straconcat("get \"", dir, "\" -", NULL);
682 unsigned char *ss = s;
684 v[n++] = "-c"; /* Execute semicolon separated commands. */
685 while ((ss = strchr(ss, '/'))) *ss = '\\'; /* Escape '/' */
686 v[n++] = s;
690 /* Optionally add SMB credentials file. */
691 optstr = get_opt_str("protocol.smb.credentials");
692 if (optstr[0]) {
693 v[n++] = "-A";
694 v[n++] = optstr;
697 v[n++] = NULL; /* End of arguments list. */
698 assert(n < MAX_SMBCLIENT_ARGS);
700 execvp(SMBCLIENT, (char **) v);
702 /* FIXME: this message will never be displayed, since execvp()
703 * failed. */
704 fprintf(stderr, SMBCLIENT " not found in $PATH");
705 _exit(1);
706 #undef MAX_SMBCLIENT_ARGS
707 #undef SMBCLIENT
710 mem_free(share);
712 conn->data_socket->fd = out_pipe[0];
713 conn->socket->fd = err_pipe[0];
715 close(out_pipe[1]);
716 close(err_pipe[1]);
718 set_handlers(out_pipe[0], (select_handler_T) smb_got_data, NULL, NULL, conn);
719 set_handlers(err_pipe[0], (select_handler_T) smb_got_text, NULL, NULL, conn);
720 set_connection_state(conn, S_CONN);