/* ce-server.c - An Assuan testbed for W32CE; server code Copyright (C) 2010 Free Software Foundation, Inc. This file is part of Assuan. Assuan is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Assuan is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN # include #else # include # include # include # include #endif #include #ifdef HAVE_W32CE_SYSTEM #ifndef FILE_ATTRIBUTE_ROMSTATICREF #define FILE_ATTRIBUTE_ROMSTATICREF FILE_ATTRIBUTE_OFFLINE #endif #endif #include "../src/assuan.h" #include "common.h" /* The port we are using by default. */ static short server_port = 15898; /* Flag set to indicate a shutdown. */ static int shutdown_pending; /* The local state of a connection. */ struct state_s { char *cwd; /* The current working directory - access using get_cwd(). */ }; typedef struct state_s *state_t; static void release_state (state_t state) { if (!state) return; xfree (state->cwd); xfree (state); } /* Helper to print a message while leaving a command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } #ifdef HAVE_W32CE_SYSTEM static char * wchar_to_utf8 (const wchar_t *string) { int n; size_t length = wcslen (string); char *result; n = WideCharToMultiByte (CP_UTF8, 0, string, length, NULL, 0, NULL, NULL); if (n < 0 || (n+1) <= 0) log_fatal ("WideCharToMultiByte failed\n"); result = xmalloc (n+1); n = WideCharToMultiByte (CP_ACP, 0, string, length, result, n, NULL, NULL); if (n < 0) log_fatal ("WideCharToMultiByte failed\n"); result[n] = 0; return result; } static wchar_t * utf8_to_wchar (const char *string) { int n; size_t length = strlen (string); wchar_t *result; size_t nbytes; n = MultiByteToWideChar (CP_UTF8, 0, string, length, NULL, 0); if (n < 0 || (n+1) <= 0) log_fatal ("MultiByteToWideChar failed\n"); nbytes = (size_t)(n+1) * sizeof(*result); if (nbytes / sizeof(*result) != (n+1)) log_fatal ("utf8_to_wchar: integer overflow\n"); result = xmalloc (nbytes); n = MultiByteToWideChar (CP_UTF8, 0, string, length, result, n); if (n < 0) log_fatal ("MultiByteToWideChar failed\n"); result[n] = 0; return result; } #endif /*HAVE_W32CE_SYSTEM*/ #ifndef HAVE_W32CE_SYSTEM static char * gnu_getcwd (void) { size_t size = 100; while (1) { char *buffer = xmalloc (size); if (getcwd (buffer, size) == buffer) return buffer; xfree (buffer); if (errno != ERANGE) return 0; size *= 2; } } #endif /*!HAVE_W32CE_SYSTEM*/ /* Return the current working directory. The returned string is valid as long as STATE->cwd is not changed. */ static const char * get_cwd (state_t state) { if (!state->cwd) { /* No working directory yet. On WindowsCE make it the module directory of this process. */ char *p; #ifdef HAVE_W32CE_SYSTEM wchar_t buf[MAX_PATH+1]; size_t n; n = GetModuleFileName (NULL, buf, MAX_PATH); if (!n) state->cwd = xstrdup ("/"); else { buf[n] = 0; state->cwd = wchar_to_utf8 (buf); p = strrchr (state->cwd, '\\'); if (p) *p = 0; } #else state->cwd = gnu_getcwd (); #endif #ifdef HAVE_W32_SYSTEM for (p=state->cwd; *p; p++) if (*p == '\\') *p = '/'; #endif /*HAVE_W32_SYSTEM*/ } return state->cwd; } static const char hlp_echo[] = "ECHO \n" "\n" "Print LINE as data lines.\n"; static gpg_error_t cmd_echo (assuan_context_t ctx, char *line) { gpg_error_t err; err = assuan_send_data (ctx, line, strlen (line)); return leave_cmd (ctx, err); } static const char hlp_pwd[] = "PWD\n" "\n" "Print the curent working directory of this session.\n"; static gpg_error_t cmd_pwd (assuan_context_t ctx, char *line) { state_t state = assuan_get_pointer (ctx); gpg_error_t err; const char *string; string = get_cwd (state); err = assuan_send_data (ctx, string, strlen (string)); return leave_cmd (ctx, err); } static const char hlp_cd[] = "CD [dir]\n" "\n" "Change the curretn directory of the session.\n"; static gpg_error_t cmd_cd (assuan_context_t ctx, char *line) { state_t state = assuan_get_pointer (ctx); gpg_error_t err = 0; char *newdir, *p; for (p=line; *p; p++) if (*p == '\\') *p = '/'; if (!*line) { xfree (state->cwd); state->cwd = NULL; get_cwd (state); } else { if (*line == '/') newdir = xstrdup (line); else newdir = xstrconcat (get_cwd (state), "/", line, NULL); while (strlen(newdir) > 1 && line[strlen(newdir)-1] == '/') line[strlen(newdir)-1] = 0; xfree (state->cwd); state->cwd = newdir; } return leave_cmd (ctx, err); } #ifdef HAVE_W32CE_SYSTEM static const char hlp_ls[] = "LS []\n" "\n" "List the files described by PATTERN.\n"; static gpg_error_t cmd_ls (assuan_context_t ctx, char *line) { state_t state = assuan_get_pointer (ctx); gpg_error_t err; WIN32_FIND_DATA fi; char buf[500]; HANDLE hd; char *p, *fname; wchar_t *wfname; if (!*line) fname = xstrconcat (get_cwd (state), "/*", NULL); else if (*line == '/' || *line == '\\') fname = xstrdup (line); else fname = xstrconcat (get_cwd (state), "/", line, NULL); for (p=fname; *p; p++) if (*p == '/') *p = '\\'; assuan_write_status (ctx, "PATTERN", fname); wfname = utf8_to_wchar (fname); xfree (fname); hd = FindFirstFile (wfname, &fi); free (wfname); if (hd == INVALID_HANDLE_VALUE) { log_info ("FindFirstFile returned %d\n", GetLastError ()); err = gpg_error_from_syserror (); /* Works for W32CE. */ goto leave; } do { DWORD attr = fi.dwFileAttributes; fname = wchar_to_utf8 (fi.cFileName); snprintf (buf, sizeof buf, "%c%c%c%c%c%c%c%c%c%c%c%c%c %7lu%c %s\n", (attr & FILE_ATTRIBUTE_DIRECTORY) ? ((attr & FILE_ATTRIBUTE_DEVICE)? 'c':'d'):'-', (attr & FILE_ATTRIBUTE_READONLY)? 'r':'-', (attr & FILE_ATTRIBUTE_HIDDEN)? 'h':'-', (attr & FILE_ATTRIBUTE_SYSTEM)? 's':'-', (attr & FILE_ATTRIBUTE_ARCHIVE)? 'a':'-', (attr & FILE_ATTRIBUTE_COMPRESSED)? 'c':'-', (attr & FILE_ATTRIBUTE_ENCRYPTED)? 'e':'-', (attr & FILE_ATTRIBUTE_INROM)? 'R':'-', (attr & FILE_ATTRIBUTE_REPARSE_POINT)? 'P':'-', (attr & FILE_ATTRIBUTE_ROMMODULE)? 'M':'-', (attr & FILE_ATTRIBUTE_ROMSTATICREF)? 'R':'-', (attr & FILE_ATTRIBUTE_SPARSE_FILE)? 'S':'-', (attr & FILE_ATTRIBUTE_TEMPORARY)? 't':'-', (unsigned long)fi.nFileSizeLow, fi.nFileSizeHigh? 'X':' ', fname); free (fname); err = assuan_send_data (ctx, buf, strlen (buf)); if (!err) err = assuan_send_data (ctx, NULL, 0); } while (!err && FindNextFile (hd, &fi)); if (err) ; else if (GetLastError () == ERROR_NO_MORE_FILES) err = 0; else { log_info ("FindNextFile returned %d\n", GetLastError ()); err = gpg_error_from_syserror (); } FindClose (hd); leave: return leave_cmd (ctx, err); } #endif /*HAVE_W32CE_SYSTEM*/ static const char hlp_shutdown[] = "SHUTDOWN\n" "\n" "Shutdown the server process after ending this connection\n"; static gpg_error_t cmd_shutdown (assuan_context_t ctx, char *line) { (void)ctx; (void)line; shutdown_pending = 1; return 0; } static gpg_error_t register_commands (assuan_context_t ctx) { static struct { const char *name; gpg_error_t (*handler) (assuan_context_t, char *line); const char * const help; } table[] = { #ifdef HAVE_W32CE_SYSTEM { "LS", cmd_ls, hlp_ls }, #endif { "PWD", cmd_pwd, hlp_pwd }, { "CD", cmd_cd, hlp_cd }, { "ECHO", cmd_echo, hlp_echo }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "SHUTDOWN", cmd_shutdown, hlp_shutdown }, { NULL, NULL } }; int i; gpg_error_t rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Startup the server. */ static void server (void) { gpg_error_t err; assuan_fd_t server_fd; assuan_sock_nonce_t server_nonce; int one = 1; struct sockaddr_in name; assuan_context_t ctx; state_t state = NULL; err = assuan_new (&ctx); if (err) log_fatal ("assuan_new failed: %s\n", gpg_strerror (err)); server_fd = assuan_sock_new (PF_INET, SOCK_STREAM, 0); if (server_fd == ASSUAN_INVALID_FD) log_fatal ("socket() failed: %s", strerror (errno)); if (setsockopt (HANDLE2SOCKET (server_fd), SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof one)) log_error ("setsockopt(SO_REUSEADDR) failed: %s", strerror (errno)); name.sin_family = AF_INET; name.sin_port = htons (server_port); name.sin_addr.s_addr = htonl (INADDR_ANY); if (assuan_sock_bind (server_fd, (struct sockaddr *) &name, sizeof name)) log_fatal ("bind() failed: %s", strerror (errno)); if (assuan_sock_get_nonce ((struct sockaddr*)&name, sizeof name, &server_nonce)) log_fatal ("assuan_sock_get_nonce failed: %s", strerror (errno)); /* Register the nonce with the context so that assuan_accept knows about it. We can't do that directly in assuan_sock_bind because we want these socket wrappers to be context neutral and drop in replacement for the standard socket functions. */ assuan_set_sock_nonce (ctx, &server_nonce); if (listen (HANDLE2SOCKET (server_fd), 5)) log_fatal ("listen() failed: %s\n", strerror (errno)); log_info ("server listening on port %hd\n", server_port); err = assuan_init_socket_server (ctx, server_fd, 0); if (err) log_fatal ("assuan_init_socket_server failed: %s\n", gpg_strerror (err)); err = register_commands (ctx); if (err) log_fatal ("register_commands failed: %s\n", gpg_strerror(err)); if (debug) assuan_set_log_stream (ctx, stderr); state = xcalloc (1, sizeof state); assuan_set_pointer (ctx, state); while (!shutdown_pending) { err = assuan_accept (ctx); if (err) { if (gpg_err_code (err) == GPG_ERR_EOF || err == -1) log_error ("assuan_accept failed: %s\n", gpg_strerror (err)); break; } log_info ("client connected. Client's pid is %ld\n", (long)assuan_get_pid (ctx)); err = assuan_process (ctx); if (err) log_error ("assuan_process failed: %s\n", gpg_strerror (err)); } assuan_sock_close (server_fd); assuan_release (ctx); release_state (state); } /* M A I N */ int main (int argc, char **argv) { gpg_error_t err; int last_argc = -1; if (argc) { log_set_prefix (*argv); argc--; argv++; } while (argc && last_argc != argc ) { last_argc = argc; if (!strcmp (*argv, "--help")) { printf ( "usage: %s [options]\n" "\n" "Options:\n" " --verbose Show what is going on\n", log_get_prefix ()); exit (0); } if (!strcmp (*argv, "--verbose")) { verbose = 1; argc--; argv++; } else if (!strcmp (*argv, "--debug")) { verbose = debug = 1; argc--; argv++; } } assuan_set_assuan_log_prefix (log_prefix); if (debug) assuan_set_assuan_log_stream (stderr); err = assuan_sock_init (); if (err) log_fatal ("assuan_sock_init failed: %s\n", gpg_strerror (err)); log_info ("server starting...\n"); server (); log_info ("server finished\n"); assuan_sock_deinit (); return errorcount ? 1 : 0; }