Merge branch 'elinks-0.12' into elinks-0.13
[elinks.git] / src / protocol / smb / smb2.c
blob5cd7def6c22530e4f4b714dba977b5024dfd97ac
1 /* SMB protocol implementation */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <errno.h>
8 #ifdef HAVE_LIBSMBCLIENT_H
9 #include <libsmbclient.h>
10 #endif
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #ifdef HAVE_FCNTL_H
15 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
16 #endif
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
21 #include "elinks.h"
23 #include "cache/cache.h"
24 #include "config/options.h"
25 #include "intl/gettext/libintl.h"
26 #include "main/module.h"
27 #include "main/select.h"
28 #include "network/connection.h"
29 #include "network/socket.h"
30 #include "osdep/osdep.h"
31 #include "protocol/auth/auth.h"
32 #include "protocol/common.h"
33 #include "protocol/protocol.h"
34 #include "protocol/smb/smb.h"
35 #include "protocol/uri.h"
36 #include "util/conv.h"
37 #include "util/memory.h"
38 #include "util/string.h"
40 /* These options are not used. */
41 #if 0
42 struct option_info smb_options[] = {
43 INIT_OPT_TREE("protocol", N_("SMB"),
44 "smb", 0,
45 N_("SAMBA specific options.")),
47 INIT_OPT_STRING("protocol.smb", N_("Credentials"),
48 "credentials", 0, "",
49 N_("Credentials file passed to smbclient via -A option.")),
51 NULL_OPTION_INFO,
53 #endif
55 struct module smb_protocol_module = struct_module(
56 /* name: */ N_("SMB"),
57 /* options: */ NULL,
58 /* hooks: */ NULL,
59 /* submodules: */ NULL,
60 /* data: */ NULL,
61 /* init: */ NULL,
62 /* done: */ NULL
65 static FILE *header_out, *data_out;
67 /* The child process generally does not bother to free the memory it
68 * allocates. When the process exits, the operating system will free
69 * the memory anyway. There is no point in changing this, because the
70 * child process also inherits memory allocations from the parent
71 * process, and it would be very cumbersome to free those. */
73 static void
74 smb_error(int error)
76 fputs("text/x-error", header_out);
77 fprintf(data_out, "%d\n", error);
78 exit(1);
81 static int
82 compare(const void *a, const void *b)
84 const struct smbc_dirent **da = (const struct smbc_dirent **)a;
85 const struct smbc_dirent **db = (const struct smbc_dirent **)b;
86 int res = (*da)->smbc_type - (*db)->smbc_type;
88 if (res) {
89 return res;
91 return strcmp((*da)->name, (*db)->name);
94 static void
95 smb_add_link(struct string *string, const struct smbc_dirent *entry,
96 const unsigned char *text, const unsigned char dircolor[])
98 struct string uri_string;
100 if (!init_string(&uri_string)) return;
101 encode_uri_string(&uri_string, entry->name, entry->namelen, 0);
103 add_to_string(string, "<a href=\"");
104 add_html_to_string(string, uri_string.source, uri_string.length);
105 done_string(&uri_string);
107 add_to_string(string, "\">");
108 if (*dircolor) {
109 add_to_string(string, "<font color=\"");
110 add_to_string(string, dircolor);
111 add_to_string(string, "\"><b>");
113 add_html_to_string(string, entry->name, entry->namelen);
114 if (*dircolor) {
115 add_to_string(string, "</b></font>");
117 add_to_string(string, "</a>");
118 if (text) add_to_string(string, text);
121 static void
122 display_entry(const struct smbc_dirent *entry, const unsigned char dircolor[])
124 static const unsigned char zero = '\0';
125 struct string string;
127 if (!init_string(&string)) return;
129 switch (entry->smbc_type) {
130 case SMBC_WORKGROUP:
131 smb_add_link(&string, entry, " WORKGROUP ", dircolor);
132 break;
133 case SMBC_SERVER:
134 smb_add_link(&string, entry, " SERVER ", dircolor);
135 if (entry->comment) {
136 add_html_to_string(&string, entry->comment, entry->commentlen);
138 break;
139 case SMBC_FILE_SHARE:
140 smb_add_link(&string, entry, " FILE SHARE ", dircolor);
141 if (entry->comment) {
142 add_html_to_string(&string, entry->comment, entry->commentlen);
144 break;
145 case SMBC_PRINTER_SHARE:
146 add_html_to_string(&string, entry->name, entry->namelen);
147 add_to_string(&string, " PRINTER ");
148 if (entry->comment) {
149 add_html_to_string(&string, entry->comment, entry->commentlen);
151 break;
152 case SMBC_COMMS_SHARE:
153 add_bytes_to_string(&string, entry->name, entry->namelen);
154 add_to_string(&string, " COMM");
155 break;
156 case SMBC_IPC_SHARE:
157 add_bytes_to_string(&string, entry->name, entry->namelen);
158 add_to_string(&string, " IPC");
159 break;
160 case SMBC_DIR:
161 smb_add_link(&string, entry, NULL, dircolor);
162 break;
163 case SMBC_LINK:
164 smb_add_link(&string, entry, " Link", &zero);
165 break;
166 case SMBC_FILE:
167 smb_add_link(&string, entry, NULL, &zero);
168 break;
169 default:
170 /* unknown type */
171 break;
173 fputs(string.source, data_out);
174 fputc('\n', data_out);
175 done_string(&string);
178 static void
179 sort_and_display_entries(int dir, const unsigned char dircolor[])
181 struct smbc_dirent *fentry, **table = NULL;
182 int size = 0;
183 int i;
185 while ((fentry = smbc_readdir(dir))) {
186 struct smbc_dirent **new_table, *new_entry;
187 unsigned int commentlen = fentry->commentlen;
188 unsigned int namelen = fentry->namelen;
190 if (!strcmp(fentry->name, "."))
191 continue;
193 /* In libsmbclient 3.0.10, @smbc_dirent.namelen and
194 * @smbc_dirent.commentlen include the null characters
195 * (tested with GDB). In libsmbclient 3.0.24, they
196 * don't. This is related to Samba bug 3030. Adjust
197 * the lengths to exclude the null characters, so that
198 * other code need not care.
200 * Make all changes to local copies rather than
201 * directly to *@fentry, so that there's no chance of
202 * ELinks messing up whatever mechanism libsmbclient
203 * will use to free @fentry. */
204 if (commentlen > 0 && fentry->comment[commentlen - 1] == '\0')
205 commentlen--;
206 if (namelen > 0 && fentry->name[namelen - 1] == '\0')
207 namelen--;
209 /* libsmbclient seems to place the struct smbc_dirent,
210 * the name string, and the comment string all in one
211 * block of memory, which then is smbc_dirent.dirlen
212 * bytes long. This has however not been really
213 * documented, so ELinks should not assume copying
214 * fentry->dirlen bytes will copy the comment too.
215 * Yet, it would be wasteful to copy both dirlen bytes
216 * and then the comment string separately. What we do
217 * here is ignore fentry->dirlen and recompute the
218 * size based on namelen. */
219 new_entry = (struct smbc_dirent *)
220 memacpy((const unsigned char *) fentry,
221 offsetof(struct smbc_dirent, name)
222 + namelen); /* memacpy appends '\0' */
223 if (!new_entry)
224 continue;
225 new_entry->namelen = namelen;
226 new_entry->commentlen = commentlen;
227 if (fentry->comment)
228 new_entry->comment = memacpy(fentry->comment, commentlen);
229 if (!new_entry->comment)
230 new_entry->commentlen = 0;
232 new_table = mem_realloc(table, (size + 1) * sizeof(*table));
233 if (!new_table)
234 continue;
235 table = new_table;
236 table[size] = new_entry;
237 size++;
239 /* If size==0, then table==NULL. According to ISO/IEC 9899:1999
240 * 7.20.5p1, the NULL must not be given to qsort. */
241 if (size > 0)
242 qsort(table, size, sizeof(*table), compare);
244 for (i = 0; i < size; i++) {
245 display_entry(table[i], dircolor);
249 static void
250 smb_directory(int dir, struct uri *uri)
252 struct string buf;
253 unsigned char dircolor[8] = "";
255 if (init_directory_listing(&buf, uri) != S_OK) {
256 smb_error(-S_OUT_OF_MEM);
259 fputs("text/html", header_out);
260 fclose(header_out);
262 fputs(buf.source, data_out);
263 fputc('\n', data_out);
265 if (get_opt_bool("document.browse.links.color_dirs", NULL)) {
266 color_to_string(get_opt_color("document.colors.dirs", NULL),
267 dircolor);
270 sort_and_display_entries(dir, dircolor);
271 fputs("</pre><hr/></body></html>\n", data_out);
272 smbc_closedir(dir);
273 exit(0);
276 static void
277 smb_auth(const char *srv, const char *shr, char *wg, int wglen, char *un,
278 int unlen, char *pw, int pwlen)
280 /* TODO */
283 #define READ_SIZE 4096
285 static void
286 do_smb(struct connection *conn)
288 struct uri *uri = conn->uri;
289 struct auth_entry *auth = find_auth(uri);
290 struct string string;
291 unsigned char *url;
292 int dir;
294 if ((uri->userlen && uri->passwordlen) || !auth) {
295 url = get_uri_string(uri, URI_BASE);
296 } else {
297 unsigned char *uri_string = get_uri_string(uri, URI_HOST | URI_PORT | URI_DATA);
299 if (!uri_string || !init_string(&string)) {
300 smb_error(-S_OUT_OF_MEM);
302 /* Must URI-encode the username and password to avoid
303 * ambiguity if they contain "/:@" characters.
304 * Libsmbclient then decodes them again, and the
305 * server gets them as they were in auth->user and
306 * auth->password, i.e. as the user typed them in the
307 * auth dialog. This implies that, if the username or
308 * password contains some characters or bytes that the
309 * user cannot directly type, then she cannot enter
310 * them. If that becomes an actual problem, it should
311 * be fixed in the auth dialog, e.g. by providing a
312 * hexadecimal input mode. */
313 add_to_string(&string, "smb://");
314 encode_uri_string(&string, auth->user, -1, 1);
315 add_char_to_string(&string, ':');
316 encode_uri_string(&string, auth->password, -1, 1);
317 add_char_to_string(&string, '@');
318 add_to_string(&string, uri_string);
319 url = string.source;
322 if (!url) {
323 smb_error(-S_OUT_OF_MEM);
325 if (smbc_init(smb_auth, 0)) {
326 smb_error(errno);
329 dir = smbc_opendir(url);
330 if (dir >= 0) {
331 smb_directory(dir, conn->uri);
332 } else {
333 const int errno_from_opendir = errno;
334 char buf[READ_SIZE];
335 struct stat sb;
336 int r, res, fdout;
337 int file = smbc_open(url, O_RDONLY, 0);
339 if (file < 0) {
340 /* If we're opening the list of shares without
341 * proper authentication, then smbc_opendir
342 * fails with EACCES and smbc_open fails with
343 * ENOENT. In this case, return the EACCES so
344 * that the parent ELinks process will prompt
345 * for credentials. */
346 if (errno == ENOENT && errno_from_opendir == EACCES)
347 errno = errno_from_opendir;
348 smb_error(errno);
351 res = smbc_fstat(file, &sb);
352 if (res) {
353 smb_error(res);
355 /* filesize */
356 fprintf(header_out, "%" OFF_PRINT_FORMAT,
357 (off_print_T) sb.st_size);
358 fclose(header_out);
360 fdout = fileno(data_out);
361 while ((r = smbc_read(file, buf, READ_SIZE)) > 0) {
362 if (safe_write(fdout, buf, r) <= 0)
363 break;
365 smbc_close(file);
366 exit(0);
370 #undef READ_SIZE
372 /* Kill the current connection and ask for a username/password for the next
373 * try. */
374 static void
375 prompt_username_pw(struct connection *conn)
377 add_auth_entry(conn->uri, "Samba", NULL, NULL, 0);
378 abort_connection(conn, S_OK);
381 static void
382 smb_got_error(struct socket *socket, struct read_buffer *rb)
384 int len = rb->length;
385 struct connection *conn = socket->conn;
386 int error;
388 if (len < 0) {
389 abort_connection(conn, -errno);
390 return;
393 /* There should be free space in the buffer, because
394 * @alloc_read_buffer allocated several kibibytes, and the
395 * child process wrote only an integer and a newline to the
396 * pipe. */
397 assert(rb->freespace >= 1);
398 if_assert_failed {
399 abort_connection(conn, S_INTERNAL);
400 return;
402 rb->data[len] = '\0';
403 error = atoi(rb->data);
404 kill_buffer_data(rb, len);
405 switch (error) {
406 case EACCES:
407 prompt_username_pw(conn);
408 break;
409 default:
410 abort_connection(conn, -error);
411 break;
415 static void
416 smb_got_data(struct socket *socket, struct read_buffer *rb)
418 int len = rb->length;
419 struct connection *conn = socket->conn;
421 if (len < 0) {
422 abort_connection(conn, -errno);
423 return;
426 if (!len) {
427 abort_connection(conn, S_OK);
428 return;
431 socket->state = SOCKET_END_ONCLOSE;
432 conn->received += len;
433 if (add_fragment(conn->cached, conn->from, rb->data, len) == 1)
434 conn->tries = 0;
435 conn->from += len;
436 kill_buffer_data(rb, len);
438 read_from_socket(socket, rb, S_TRANS, smb_got_data);
441 static void
442 smb_got_header(struct socket *socket, struct read_buffer *rb)
444 struct connection *conn = socket->conn;
445 struct read_buffer *buf;
446 int error = 0;
448 conn->cached = get_cache_entry(conn->uri);
449 if (!conn->cached) {
450 /* Even though these are pipes rather than real
451 * sockets, call close_socket instead of close, to
452 * ensure that abort_connection won't try to close the
453 * file descriptors again. (Could we skip the calls
454 * and assume abort_connection will do them?) */
455 close_socket(socket);
456 close_socket(conn->data_socket);
457 abort_connection(conn, S_OUT_OF_MEM);
458 return;
460 socket->state = SOCKET_END_ONCLOSE;
462 if (rb->length > 0) {
463 unsigned char *ctype = memacpy(rb->data, rb->length);
465 if (ctype && *ctype) {
466 if (!strcmp(ctype, "text/x-error")) {
467 error = 1;
468 mem_free(ctype);
469 } else {
470 if (ctype[0] >= '0' && ctype[0] <= '9') {
471 #ifdef HAVE_ATOLL
472 conn->est_length = (off_t)atoll(ctype);
473 #else
474 conn->est_length = (off_t)atol(ctype);
475 #endif
476 mem_free(ctype);
478 /* avoid error */
479 if (!conn->est_length) {
480 abort_connection(conn, S_OK);
481 return;
484 else mem_free_set(&conn->cached->content_type, ctype);
486 } else {
487 mem_free_if(ctype);
491 buf = alloc_read_buffer(conn->data_socket);
492 if (!buf) {
493 close_socket(socket);
494 close_socket(conn->data_socket);
495 abort_connection(conn, S_OUT_OF_MEM);
496 return;
498 if (error) {
499 mem_free_set(&conn->cached->content_type, stracpy("text/html"));
500 read_from_socket(conn->data_socket, buf, S_CONN, smb_got_error);
501 } else {
502 read_from_socket(conn->data_socket, buf, S_CONN, smb_got_data);
506 static void
507 close_all_fds_but_two(int header, int data)
509 int n;
510 int max = 1024;
511 #ifdef RLIMIT_NOFILE
512 struct rlimit lim;
514 if (!getrlimit(RLIMIT_NOFILE, &lim))
515 max = lim.rlim_max;
516 #endif
517 for (n = 3; n < max; n++) {
518 if (n != header && n != data) close(n);
524 void
525 smb_protocol_handler(struct connection *conn)
527 int smb_pipe[2] = { -1, -1 };
528 int header_pipe[2] = { -1, -1 };
529 pid_t cpid;
531 if (c_pipe(smb_pipe) || c_pipe(header_pipe)) {
532 int s_errno = errno;
534 if (smb_pipe[0] >= 0) close(smb_pipe[0]);
535 if (smb_pipe[1] >= 0) close(smb_pipe[1]);
536 if (header_pipe[0] >= 0) close(header_pipe[0]);
537 if (header_pipe[1] >= 0) close(header_pipe[1]);
538 abort_connection(conn, -s_errno);
539 return;
541 conn->from = 0;
542 conn->unrestartable = 1;
543 find_auth(conn->uri); /* remember username and password */
545 cpid = fork();
546 if (cpid == -1) {
547 int s_errno = errno;
549 close(smb_pipe[0]);
550 close(smb_pipe[1]);
551 close(header_pipe[0]);
552 close(header_pipe[1]);
553 retry_connection(conn, -s_errno);
554 return;
557 if (!cpid) {
558 dup2(open("/dev/null", O_RDONLY), 0);
559 close(1);
560 close(2);
561 data_out = fdopen(smb_pipe[1], "w");
562 header_out = fdopen(header_pipe[1], "w");
564 if (!data_out || !header_out) exit(1);
566 close(smb_pipe[0]);
567 close(header_pipe[0]);
569 /* There may be outgoing data in stdio buffers
570 * inherited from the parent process. The parent
571 * process is going to write this data, so the child
572 * process must not do that. Closing the file
573 * descriptors ensures this.
575 * FIXME: If something opens more files and gets the
576 * same file descriptors and does not close them
577 * before exit(), then stdio may attempt to write the
578 * buffers to the wrong files. This might happen for
579 * example if libsmbclient calls syslog(). */
581 close_all_fds_but_two(smb_pipe[1], header_pipe[1]);
582 do_smb(conn);
584 } else {
585 struct read_buffer *buf2;
587 conn->data_socket->fd = smb_pipe[0];
588 conn->socket->fd = header_pipe[0];
589 set_nonblocking_fd(conn->data_socket->fd);
590 set_nonblocking_fd(conn->socket->fd);
591 close(smb_pipe[1]);
592 close(header_pipe[1]);
593 buf2 = alloc_read_buffer(conn->socket);
594 if (!buf2) {
595 close_socket(conn->data_socket);
596 close_socket(conn->socket);
597 abort_connection(conn, S_OUT_OF_MEM);
598 return;
600 read_from_socket(conn->socket, buf2, S_CONN, smb_got_header);