1 /* Internal SMB protocol implementation */
4 #define _GNU_SOURCE /* Needed for asprintf() */
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.
18 #include <sys/types.h>
20 #include <fcntl.h> /* OS/2 needs this after sys/types.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 */
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. */
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"),
72 N_("SAMBA specific options.")),
74 INIT_OPT_STRING("protocol.smb", N_("Credentials"),
76 N_("Credentials file passed to smbclient via -A option.")),
81 struct module smb_protocol_module
= struct_module(
82 /* name: */ N_("SMB"),
83 /* options: */ smb_options
,
85 /* submodules: */ NULL
,
92 /* Return 0 if @conn->cached was set. */
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
);
106 #define READ_SIZE 4096
109 smb_read_data(struct connection
*conn
, int sock
, unsigned char *dst
)
112 struct smb_connection_info
*si
= conn
->info
;
114 r
= read(sock
, dst
, READ_SIZE
);
116 retry_connection(conn
, -errno
);
122 clear_handlers(sock
);
125 end_smb_connection(conn
);
133 smb_read_text(struct connection
*conn
, int sock
)
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
143 abort_connection(conn
, S_OUT_OF_MEM
);
148 r
= smb_read_data(conn
, sock
, si
->text
+ si
->textlen
);
151 if (!conn
->from
) set_connection_state(conn
, S_GETH
);
156 smb_got_data(struct connection
*conn
)
158 struct smb_connection_info
*si
= conn
->info
;
159 unsigned char buffer
[READ_SIZE
];
162 if (si
->list_type
!= SMB_LIST_NONE
) {
163 smb_read_text(conn
, conn
->data_socket
->fd
);
167 r
= smb_read_data(conn
, conn
->data_socket
->fd
, buffer
);
170 set_connection_state(conn
, S_TRANS
);
172 if (smb_get_cache(conn
)) return;
175 if (add_fragment(conn
->cached
, conn
->from
, buffer
, r
) == 1)
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
,
195 unsigned char *p
= strstr(line
, str1
);
199 p
= strstr(p
+ strlen(str1
), str2
);
204 parse_smbclient_output(struct uri
*uri
, struct smb_connection_info
*si
,
207 unsigned char *line_start
, *line_end
;
213 } type
= SMB_TYPE_NONE
;
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
;
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. */
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");
252 type
= SMB_TYPE_SHARE
;
256 found
= find_strs(line
, "Server", "Comment");
258 type
= SMB_TYPE_SERVER
;
262 found
= find_strs(line
, "Workgroup", "Master");
265 type
= SMB_TYPE_WORKGROUP
;
269 if (type
== SMB_TYPE_NONE
)
272 for (ll
= line
; *ll
; ll
++)
273 if (!isspace(*ll
) && *ll
!= '-')
279 for (ll
= line
; *ll
; ll
++)
283 for (lll
= ll
; *lll
; lll
++)
292 if (!strstr(lll
, "Disk"))
295 if (pos
&& pos
< line_len
296 && isspace(*(llll
= line
+ pos
- 1))
298 while (llll
> ll
&& isspace(*llll
))
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
);
314 case SMB_TYPE_WORKGROUP
:
315 if (pos
< line_len
&& pos
316 && isspace(line
[pos
- 1])
317 && !isspace(line
[pos
])) {
320 for (ll
= lll
; *ll
; ll
++)
324 for (lll
= ll
; *lll
; lll
++)
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
);
343 } else if (si
->list_type
== SMB_LIST_DIR
) {
344 if (strstr(line
, "NT_STATUS")) {
345 /* Error, stop after message. */
350 if (line_end
- line_start
>= 5
351 && line_start
[0] == ' '
352 && line_start
[1] == ' '
353 && line_start
[2] != ' ') {
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,
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()? */
376 while (p
> url
&& isdigit(*p
)) p
--;
377 if (p
== url
|| !isspace(*p
)) goto print_as_is
;
378 while (p
> url
&& isspace(*p
)) p
--;
381 while (p
> url
&& isdigit(*p
)) p
--;
382 if (p
== url
|| *p
!= ':') goto print_as_is
;
386 while (p
> url
&& isdigit(*p
)) p
--;
387 if (p
== url
|| *p
!= ':') goto print_as_is
;
391 while (p
> url
&& isdigit(*p
)) p
--;
392 if (p
== url
|| !isspace(*p
)) goto print_as_is
;
396 while (p
> url
&& isdigit(*p
)) p
--;
397 while (p
> url
&& isspace(*p
)) p
--;
398 if (p
== url
) goto print_as_is
;
401 while (p
> url
&& !isspace(*p
)) p
--;
402 if (p
== url
|| !isspace(*p
)) goto print_as_is
;
406 while (p
> url
&& !isspace(*p
)) p
--;
407 if (p
== url
|| !isspace(*p
)) goto print_as_is
;
408 while (p
> url
&& isspace(*p
)) p
--;
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
--;
434 if (*pp
== 'D' && may_be_dir
)
439 while (pp
> url
&& isspace(*pp
)) pp
--;
443 /* Don't display '.' directory */
444 if (p
== url
&& *url
== '.') goto ignored
;
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
);
461 add_bytes_to_string(page
, line_start
, line_len
);
464 add_char_to_string(page
, ASCII_LF
);
466 line_start
= line_end
+ start_offset
+ 1;
469 add_to_string(page
, "</pre></body></html>");
473 end_smb_connection(struct connection
*conn
)
475 struct smb_connection_info
*si
= conn
->info
;
477 enum connection_state state
= S_OK
;
479 if (smb_get_cache(conn
)) return;
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';
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);
502 if (!init_string(&page
)) {
503 state
= S_OUT_OF_MEM
;
507 parse_smbclient_output(uri
, si
, &page
);
509 add_fragment(conn
->cached
, 0, page
.source
, page
.length
);
510 conn
->from
+= page
.length
;
513 mem_free_set(&conn
->cached
->content_type
, stracpy("text/html"));
517 close_socket(conn
->socket
);
518 close_socket(conn
->data_socket
);
519 abort_connection(conn
, state
);
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
;
532 struct smb_connection_info
*si
;
535 si
= mem_calloc(1, sizeof(*si
) + 2);
537 abort_connection(conn
, S_OUT_OF_MEM
);
543 p
= strchr(uri
->data
, '/');
544 if (p
&& p
- uri
->data
< uri
->datalen
) {
545 share
= memacpy(uri
->data
, p
- uri
->data
);
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
);
562 abort_connection(conn
, S_OUT_OF_MEM
);
566 dirlen
= strlen(dir
);
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
)) {
577 if (out_pipe
[0] >= 0) close(out_pipe
[0]);
578 if (out_pipe
[1] >= 0) close(out_pipe
[1]);
580 abort_connection(conn
, -s_errno
);
595 retry_connection(conn
, -s_errno
);
600 #define SMBCLIENT "smbclient"
601 #define MAX_SMBCLIENT_ARGS 32
603 unsigned char *v
[MAX_SMBCLIENT_ARGS
];
604 unsigned char *optstr
;
607 dup2(out_pipe
[1], 1);
609 dup2(err_pipe
[1], 2);
611 dup2(open("/dev/null", O_RDONLY
), 0);
613 close_all_non_term_fd();
617 /* Usage: smbclient service <password> [options] */
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 */
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
);
638 /* Get a list of shares available on a host. */
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. */
648 /* Connect to the specified port. */
650 v
[n
++] = memacpy(uri
->port
, uri
->portlen
);
654 /* Set the network username. */
656 if (!uri
->passwordlen
) {
658 v
[n
++] = memacpy(uri
->user
, uri
->userlen
);
661 asprintf((char **) &v
[n
++], "%.*s%%%.*s",
662 uri
->userlen
, uri
->user
,
663 uri
->passwordlen
, uri
->password
);
668 /* FIXME: use si->list_type here ?? --Zas */
669 if (!dirlen
|| dir
[dirlen
- 1] == '/' || dir
[dirlen
- 1] == '\\') {
671 /* Initial directory. */
676 v
[n
++] = "-c"; /* Execute semicolon separated commands. */
677 v
[n
++] = "ls"; /* List files. */
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 '/' */
690 /* Optionally add SMB credentials file. */
691 optstr
= get_opt_str("protocol.smb.credentials");
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()
704 fprintf(stderr
, SMBCLIENT
" not found in $PATH");
706 #undef MAX_SMBCLIENT_ARGS
712 conn
->data_socket
->fd
= out_pipe
[0];
713 conn
->socket
->fd
= err_pipe
[0];
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
);