X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source3%2Fsmbd%2Fserver.c;h=ca6f6c8e16b2fcc8cb62b74dc9947c1d7c1dda58;hb=aff002e829b8169997f7f446959e2f1d5baf3a60;hp=346e8973de3948aecddaa59d66eab2badf222c96;hpb=258ae4cec596631b758fb17c170c4494e4db8a8e;p=samba.git diff --git a/source3/smbd/server.c b/source3/smbd/server.c index 346e8973de3..ca6f6c8e16b 100644 --- a/source3/smbd/server.c +++ b/source3/smbd/server.c @@ -22,7 +22,29 @@ */ #include "includes.h" +#include "popt_common.h" #include "smbd/globals.h" +#include "librpc/gen_ndr/messaging.h" +#include "registry.h" +#include "libcli/auth/schannel.h" +#include "secrets.h" + +#include "../librpc/gen_ndr/srv_dfs.h" +#include "../librpc/gen_ndr/srv_dssetup.h" +#include "../librpc/gen_ndr/srv_echo.h" +#include "../librpc/gen_ndr/srv_eventlog.h" +#include "../librpc/gen_ndr/srv_initshutdown.h" +#include "../librpc/gen_ndr/srv_lsa.h" +#include "../librpc/gen_ndr/srv_netlogon.h" +#include "../librpc/gen_ndr/srv_ntsvcs.h" +#include "../librpc/gen_ndr/srv_samr.h" +#include "../librpc/gen_ndr/srv_spoolss.h" +#include "../librpc/gen_ndr/srv_srvsvc.h" +#include "../librpc/gen_ndr/srv_svcctl.h" +#include "../librpc/gen_ndr/srv_winreg.h" +#include "../librpc/gen_ndr/srv_wkssvc.h" + +#include "printing/nt_printing_migrate.h" static_decl_rpc; @@ -47,39 +69,7 @@ int get_client_fd(void) struct event_context *smbd_event_context(void) { - if (!smbd_event_ctx) { - smbd_event_ctx = event_context_init(talloc_autofree_context()); - } - if (!smbd_event_ctx) { - smb_panic("Could not init smbd event context"); - } - return smbd_event_ctx; -} - -struct messaging_context *smbd_messaging_context(void) -{ - if (smbd_msg_ctx == NULL) { - smbd_msg_ctx = messaging_init(talloc_autofree_context(), - server_id_self(), - smbd_event_context()); - } - if (smbd_msg_ctx == NULL) { - DEBUG(0, ("Could not init smbd messaging context.\n")); - } - return smbd_msg_ctx; -} - -struct memcache *smbd_memcache(void) -{ - if (!smbd_memcache_ctx) { - smbd_memcache_ctx = memcache_init(talloc_autofree_context(), - lp_max_stat_cache_size()*1024); - } - if (!smbd_memcache_ctx) { - smb_panic("Could not init smbd memcache"); - } - - return smbd_memcache_ctx; + return server_event_context(); } /******************************************************************* @@ -94,7 +84,8 @@ static void smb_conf_updated(struct messaging_context *msg, { DEBUG(10,("smb_conf_updated: Got message saying smb.conf was " "updated. Reloading.\n")); - reload_services(False); + change_to_root_user(); + reload_services(msg, False); } @@ -156,7 +147,6 @@ static void msg_inject_fault(struct messaging_context *msg, int sig; if (data->length != sizeof(sig)) { - DEBUG(0, ("Process %s sent bogus signal injection request\n", procid_str_static(&src))); return; @@ -180,19 +170,33 @@ static void msg_inject_fault(struct messaging_context *msg, } #endif /* DEVELOPER */ -struct child_pid { - struct child_pid *prev, *next; - pid_t pid; -}; - -static void add_child_pid(pid_t pid) +/* + * Parent smbd process sets its own debug level first and then + * sends a message to all the smbd children to adjust their debug + * level to that of the parent. + */ + +static void smbd_msg_debug(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) { struct child_pid *child; - if (lp_max_smbd_processes() == 0) { - /* Don't bother with the child list if we don't care anyway */ - return; + debug_message(msg_ctx, private_data, MSG_DEBUG, server_id, data); + + for (child = children; child != NULL; child = child->next) { + messaging_send_buf(msg_ctx, pid_to_procid(child->pid), + MSG_DEBUG, + data->data, + strlen((char *) data->data) + 1); } +} + +static void add_child_pid(pid_t pid) +{ + struct child_pid *child; child = SMB_MALLOC_P(struct child_pid); if (child == NULL) { @@ -204,24 +208,62 @@ static void add_child_pid(pid_t pid) num_children += 1; } +/* + at most every smbd:cleanuptime seconds (default 20), we scan the BRL + and locking database for entries to cleanup. As a side effect this + also cleans up dead entries in the connections database (due to the + traversal in message_send_all() + + Using a timer for this prevents a flood of traversals when a large + number of clients disconnect at the same time (perhaps due to a + network outage). +*/ + +static void cleanup_timeout_fn(struct event_context *event_ctx, + struct timed_event *te, + struct timeval now, + void *private_data) +{ + struct timed_event **cleanup_te = (struct timed_event **)private_data; + + DEBUG(1,("Cleaning up brl and lock database after unclean shutdown\n")); + message_send_all(smbd_messaging_context(), MSG_SMB_UNLOCK, NULL, 0, NULL); + messaging_send_buf(smbd_messaging_context(), procid_self(), + MSG_SMB_BRL_VALIDATE, NULL, 0); + /* mark the cleanup as having been done */ + (*cleanup_te) = NULL; +} + static void remove_child_pid(pid_t pid, bool unclean_shutdown) { struct child_pid *child; + static struct timed_event *cleanup_te; + struct server_id child_id; if (unclean_shutdown) { - /* a child terminated uncleanly so tickle all processes to see - if they can grab any of the pending locks - */ - DEBUG(3,(__location__ " Unclean shutdown of pid %u\n", (unsigned int)pid)); - messaging_send_buf(smbd_messaging_context(), procid_self(), - MSG_SMB_BRL_VALIDATE, NULL, 0); - message_send_all(smbd_messaging_context(), - MSG_SMB_UNLOCK, NULL, 0, NULL); + /* a child terminated uncleanly so tickle all + processes to see if they can grab any of the + pending locks + */ + DEBUG(3,(__location__ " Unclean shutdown of pid %u\n", + (unsigned int)pid)); + if (!cleanup_te) { + /* call the cleanup timer, but not too often */ + int cleanup_time = lp_parm_int(-1, "smbd", "cleanuptime", 20); + cleanup_te = event_add_timed(smbd_event_context(), NULL, + timeval_current_ofs(cleanup_time, 0), + cleanup_timeout_fn, + &cleanup_te); + DEBUG(1,("Scheduled cleanup of brl and lock database after unclean shutdown\n")); + } } - if (lp_max_smbd_processes() == 0) { - /* Don't bother with the child list if we don't care anyway */ - return; + child_id = procid_self(); /* Just initialize pid and potentially vnn */ + child_id.pid = pid; + + if (!serverid_deregister(child_id)) { + DEBUG(1, ("Could not remove pid %d from serverid.tdb\n", + (int)pid)); } for (child = children; child != NULL; child = child->next) { @@ -330,8 +372,9 @@ static void smbd_accept_connection(struct tevent_context *ev, struct sockaddr_storage addr; socklen_t in_addrlen = sizeof(addr); pid_t pid = 0; + uint64_t unique_id; - smbd_set_server_fd(accept(s->fd,(struct sockaddr *)&addr,&in_addrlen)); + smbd_set_server_fd(accept(s->fd, (struct sockaddr *)(void *)&addr,&in_addrlen)); if (smbd_server_fd() == -1 && errno == EINTR) return; @@ -343,7 +386,7 @@ static void smbd_accept_connection(struct tevent_context *ev, } if (s->parent->interactive) { - smbd_process(); + smbd_process(smbd_server_conn); exit_server_cleanly("end of interactive mode"); return; } @@ -354,11 +397,21 @@ static void smbd_accept_connection(struct tevent_context *ev, return; } + /* + * Generate a unique id in the parent process so that we use + * the global random state in the parent. + */ + generate_random_buffer((uint8_t *)&unique_id, sizeof(unique_id)); + pid = sys_fork(); if (pid == 0) { + NTSTATUS status = NT_STATUS_OK; + /* Child code ... */ am_parent = 0; + set_my_unique_id(unique_id); + /* Stop zombies, the parent explicitly handles * them, counting worker smbds. */ CatchChild(); @@ -374,21 +427,39 @@ static void smbd_accept_connection(struct tevent_context *ev, talloc_free(s->parent); s = NULL; - if (!reinit_after_fork( - smbd_messaging_context(), - smbd_event_context(), - true)) { + status = reinit_after_fork(smbd_messaging_context(), + smbd_event_context(), procid_self(), + true); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, + NT_STATUS_TOO_MANY_OPENED_FILES)) { + DEBUG(0,("child process cannot initialize " + "because too many files are open\n")); + goto exit; + } DEBUG(0,("reinit_after_fork() failed\n")); smb_panic("reinit_after_fork() failed"); } smbd_setup_sig_term_handler(); - smbd_setup_sig_hup_handler(); + smbd_setup_sig_hup_handler(server_event_context(), + server_messaging_context()); + + if (!serverid_register(procid_self(), + FLAG_MSG_GENERAL|FLAG_MSG_SMBD + |FLAG_MSG_DBWRAP + |FLAG_MSG_PRINT_GENERAL)) { + exit_server_cleanly("Could not register myself in " + "serverid.tdb"); + } - smbd_process(); + smbd_process(smbd_server_conn); + exit: exit_server_cleanly("end of child"); return; - } else if (pid < 0) { + } + + if (pid < 0) { DEBUG(0,("smbd_accept_connection: sys_fork() failed: %s\n", strerror(errno))); } @@ -614,8 +685,14 @@ static bool open_sockets_smbd(struct smbd_parent_context *parent, clustered mode, ctdb won't allow us to start doing database operations until it has gone thru a full startup, which includes checking to see that smbd is listening. */ - claim_connection(NULL,"", - FLAG_MSG_GENERAL|FLAG_MSG_SMBD|FLAG_MSG_DBWRAP); + + if (!serverid_register(procid_self(), + FLAG_MSG_GENERAL|FLAG_MSG_SMBD + |FLAG_MSG_DBWRAP)) { + DEBUG(0, ("open_sockets_smbd: Failed to register " + "myself in serverid.tdb\n")); + return false; + } /* Listen to messages */ @@ -629,11 +706,14 @@ static bool open_sockets_smbd(struct smbd_parent_context *parent, MSG_SMB_CONF_UPDATED, smb_conf_updated); messaging_register(smbd_messaging_context(), NULL, MSG_SMB_STAT_CACHE_DELETE, smb_stat_cache_delete); + messaging_register(smbd_messaging_context(), NULL, + MSG_DEBUG, smbd_msg_debug); brl_register_msgs(smbd_messaging_context()); #ifdef CLUSTER_SUPPORT if (lp_clustering()) { - ctdbd_register_reconfigure(messaging_ctdbd_connection()); + ctdbd_register_reconfigure( + messaging_ctdbd_connection(procid_self())); } #endif @@ -643,8 +723,19 @@ static bool open_sockets_smbd(struct smbd_parent_context *parent, #endif if (dns_port != 0) { +#ifdef WITH_DNSSD_SUPPORT smbd_setup_mdns_registration(smbd_event_context(), parent, dns_port); +#endif +#ifdef WITH_AVAHI_SUPPORT + void *avahi_conn; + + avahi_conn = avahi_start_register( + smbd_event_context(), smbd_event_context(), dns_port); + if (avahi_conn == NULL) { + DEBUG(10, ("avahi_start_register failed\n")); + } +#endif } return true; @@ -654,239 +745,22 @@ static void smbd_parent_loop(struct smbd_parent_context *parent) { /* now accept incoming connections - forking a new process for each incoming connection */ - DEBUG(2,("waiting for a connection\n")); + DEBUG(2,("waiting for connections\n")); while (1) { - struct timeval now, idle_timeout; - fd_set r_fds, w_fds; - int maxfd = 0; - int num; + int ret; TALLOC_CTX *frame = talloc_stackframe(); - if (run_events(smbd_event_context(), 0, NULL, NULL)) { - TALLOC_FREE(frame); - continue; + ret = tevent_loop_once(smbd_event_context()); + if (ret != 0) { + exit_server_cleanly("tevent_loop_once() error"); } - idle_timeout = timeval_zero(); - - FD_ZERO(&w_fds); - FD_ZERO(&r_fds); - GetTimeOfDay(&now); - - event_add_to_select_args(smbd_event_context(), &now, - &r_fds, &w_fds, &idle_timeout, - &maxfd); - - num = sys_select(maxfd+1,&r_fds,&w_fds,NULL, - timeval_is_zero(&idle_timeout) ? - NULL : &idle_timeout); - - /* check if we need to reload services */ - check_reload(time(NULL)); - - if (run_events(smbd_event_context(), num, &r_fds, &w_fds)) { - TALLOC_FREE(frame); - continue; - } - - /* socket error */ - if (num < 0) - exit_server_cleanly("socket error"); - - /* If the idle timeout fired and we don't have any connected - * users, exit gracefully. We should be running under a process - * controller that will restart us if necessry. - */ - if (num == 0 && count_all_current_connections() == 0) { - exit_server_cleanly("idle timeout"); - } TALLOC_FREE(frame); } /* end while 1 */ /* NOTREACHED return True; */ } -/**************************************************************************** - Reload printers -**************************************************************************/ -void reload_printers(void) -{ - int snum; - int n_services = lp_numservices(); - int pnum = lp_servicenumber(PRINTERS_NAME); - const char *pname; - - pcap_cache_reload(); - - /* remove stale printers */ - for (snum = 0; snum < n_services; snum++) { - /* avoid removing PRINTERS_NAME or non-autoloaded printers */ - if (snum == pnum || !(lp_snum_ok(snum) && lp_print_ok(snum) && - lp_autoloaded(snum))) - continue; - - pname = lp_printername(snum); - if (!pcap_printername_ok(pname)) { - DEBUG(3, ("removing stale printer %s\n", pname)); - - if (is_printer_published(NULL, snum, NULL)) - nt_printer_publish(NULL, snum, SPOOL_DS_UNPUBLISH); - del_a_printer(pname); - lp_killservice(snum); - } - } - - load_printers(); -} - -/**************************************************************************** - Reload the services file. -**************************************************************************/ - -bool reload_services(bool test) -{ - bool ret; - - if (lp_loaded()) { - char *fname = lp_configfile(); - if (file_exist(fname) && - !strcsequal(fname, get_dyn_CONFIGFILE())) { - set_dyn_CONFIGFILE(fname); - test = False; - } - } - - reopen_logs(); - - if (test && !lp_file_list_changed()) - return(True); - - lp_killunused(conn_snum_used); - - ret = lp_load(get_dyn_CONFIGFILE(), False, False, True, True); - - reload_printers(); - - /* perhaps the config filename is now set */ - if (!test) - reload_services(True); - - reopen_logs(); - - load_interfaces(); - - if (smbd_server_fd() != -1) { - set_socket_options(smbd_server_fd(),"SO_KEEPALIVE"); - set_socket_options(smbd_server_fd(), lp_socket_options()); - } - - mangle_reset_cache(); - reset_stat_cache(); - - /* this forces service parameters to be flushed */ - set_current_service(NULL,0,True); - - return(ret); -} - -/**************************************************************************** - Exit the server. -****************************************************************************/ - -/* Reasons for shutting down a server process. */ -enum server_exit_reason { SERVER_EXIT_NORMAL, SERVER_EXIT_ABNORMAL }; - -static void exit_server_common(enum server_exit_reason how, - const char *const reason) _NORETURN_; - -static void exit_server_common(enum server_exit_reason how, - const char *const reason) -{ - bool had_open_conn; - - if (!exit_firsttime) - exit(0); - exit_firsttime = false; - - change_to_root_user(); - - if (negprot_global_auth_context) { - (negprot_global_auth_context->free)(&negprot_global_auth_context); - } - - had_open_conn = conn_close_all(); - - invalidate_all_vuids(); - - /* 3 second timeout. */ - print_notify_send_messages(smbd_messaging_context(), 3); - - /* delete our entry in the connections database. */ - yield_connection(NULL,""); - -#ifdef WITH_DFS - if (dcelogin_atmost_once) { - dfs_unlogin(); - } -#endif - -#ifdef USE_DMAPI - /* Destroy Samba DMAPI session only if we are master smbd process */ - if (am_parent) { - if (!dmapi_destroy_session()) { - DEBUG(0,("Unable to close Samba DMAPI session\n")); - } - } -#endif - - locking_end(); - printing_end(); - - if (how != SERVER_EXIT_NORMAL) { - int oldlevel = DEBUGLEVEL; - - DEBUGLEVEL = 10; - - DEBUGSEP(0); - DEBUG(0,("Abnormal server exit: %s\n", - reason ? reason : "no explanation provided")); - DEBUGSEP(0); - - log_stack_trace(); - - DEBUGLEVEL = oldlevel; - dump_core(); - - } else { - DEBUG(3,("Server exit (%s)\n", - (reason ? reason : "normal exit"))); - } - - /* if we had any open SMB connections when we exited then we - need to tell the parent smbd so that it can trigger a retry - of any locks we may have been holding or open files we were - blocking */ - if (had_open_conn) { - exit(1); - } else { - exit(0); - } -} - -void exit_server(const char *const explanation) -{ - exit_server_common(SERVER_EXIT_ABNORMAL, explanation); -} - -void exit_server_cleanly(const char *const explanation) -{ - exit_server_common(SERVER_EXIT_NORMAL, explanation); -} - -void exit_server_fault(void) -{ - exit_server("critical server fault"); -} /**************************************************************************** Initialise connect, service and file structs. @@ -902,18 +776,21 @@ static bool init_structs(void ) if (!init_names()) return False; - conn_init(); - file_init(); - init_dptrs(); - if (!secrets_init()) return False; return True; } +static bool spoolss_init_cb(void *ptr) +{ + struct messaging_context *msg_ctx = talloc_get_type_abort( + ptr, struct messaging_context); + return nt_printing_tdb_migrate(msg_ctx); +} + /**************************************************************************** main program. ****************************************************************************/ @@ -960,6 +837,12 @@ extern void build_options(bool screen); }; struct smbd_parent_context *parent = NULL; TALLOC_CTX *frame = talloc_stackframe(); /* Setup tos. */ + NTSTATUS status; + uint64_t unique_id; + struct rpc_srv_callbacks spoolss_cb; + + /* Initialize the event context, it will panic on error */ + smbd_event_context(); smbd_init_globals(); @@ -1018,8 +901,6 @@ extern void build_options(bool screen); setluid(0); #endif - sec_init(); - set_remote_machine_name("smbd", False); if (interactive && (DEBUGLEVEL >= 9)) { @@ -1035,9 +916,11 @@ extern void build_options(bool screen); client problems at a later date. (tridge) */ generate_random_buffer(NULL, 0); + /* get initial effective uid and gid */ + sec_init(); + /* make absolutely sure we run as root - to handle cases where people are crazy enough to have it setuid */ - gain_root_privilege(); gain_root_group_privilege(); @@ -1063,12 +946,15 @@ extern void build_options(bool screen); BlockSignals(False, SIGUSR1); BlockSignals(False, SIGTERM); + /* Ensure we leave no zombies until we + * correctly set up child handling below. */ + + CatchChild(); + /* we want total control over the permissions on created files, so set our umask to 0 */ umask(0); - init_sec_ctx(); - reopen_logs(); DEBUG(0,("smbd version %s started.\n", samba_version_string())); @@ -1090,10 +976,13 @@ extern void build_options(bool screen); exit(1); } + /* Init the security context and global current_user */ + init_sec_ctx(); + if (smbd_messaging_context() == NULL) exit(1); - if (!reload_services(False)) + if (!reload_services(smbd_messaging_context(), False)) return(-1); init_structs(); @@ -1129,9 +1018,12 @@ extern void build_options(bool screen); if (is_daemon && !interactive) { DEBUG( 3, ( "Becoming a daemon.\n" ) ); - become_daemon(Fork, no_process_group); + become_daemon(Fork, no_process_group, log_stdout); } + generate_random_buffer((uint8_t *)&unique_id, sizeof(unique_id)); + set_my_unique_id(unique_id); + #if HAVE_SETPGID /* * If we're interactive we want to set our own process group for @@ -1147,14 +1039,19 @@ extern void build_options(bool screen); if (is_daemon) pidfile_create("smbd"); - if (!reinit_after_fork(smbd_messaging_context(), - smbd_event_context(), false)) { + status = reinit_after_fork(smbd_messaging_context(), + smbd_event_context(), + procid_self(), false); + if (!NT_STATUS_IS_OK(status)) { DEBUG(0,("reinit_after_fork() failed\n")); exit(1); } + smbd_server_conn->msg_ctx = smbd_messaging_context(); + smbd_setup_sig_term_handler(); - smbd_setup_sig_hup_handler(); + smbd_setup_sig_hup_handler(smbd_event_context(), + smbd_server_conn->msg_ctx); /* Setup all the TDB's - including CLEAR_IF_FIRST tdb's. */ @@ -1175,13 +1072,21 @@ extern void build_options(bool screen); exit(1); } + if (lp_server_role() == ROLE_DOMAIN_BDC || lp_server_role() == ROLE_DOMAIN_PDC) { + if (!open_schannel_session_store(NULL, lp_private_dir())) { + DEBUG(0,("ERROR: Samba cannot open schannel store for secured NETLOGON operations.\n")); + exit(1); + } + } + if(!get_global_sam_sid()) { DEBUG(0,("ERROR: Samba cannot create a SAM SID.\n")); exit(1); } - if (!session_init()) + if (!sessionid_init()) { exit(1); + } if (!connections_init(True)) exit(1); @@ -1189,24 +1094,68 @@ extern void build_options(bool screen); if (!locking_init()) exit(1); - namecache_enable(); + if (!messaging_tdb_parent_init()) { + exit(1); + } + + if (!notify_internal_parent_init()) { + exit(1); + } + + if (!serverid_parent_init()) { + exit(1); + } if (!W_ERROR_IS_OK(registry_init_full())) exit(1); + if (!print_backend_init(smbd_messaging_context())) + exit(1); + #if 0 if (!init_svcctl_db()) exit(1); #endif - if (!print_backend_init(smbd_messaging_context())) + /* Open the share_info.tdb here, so we don't have to open + after the fork on every single connection. This is a small + performance improvment and reduces the total number of system + fds used. */ + if (!share_info_db_init()) { + DEBUG(0,("ERROR: failed to load share info db.\n")); exit(1); + } + + if (!init_system_info()) { + DEBUG(0,("ERROR: failed to setup system user info.\n")); + return -1; + } if (!init_guest_info()) { DEBUG(0,("ERROR: failed to setup guest info.\n")); return -1; } + /* + * Initialize spoolss with an init function to convert printers first. + * static_init_rpc will try to initialize the spoolss server too but you + * can't register it twice. + */ + spoolss_cb.init = spoolss_init_cb; + spoolss_cb.shutdown = NULL; + spoolss_cb.private_data = smbd_server_conn->msg_ctx; + + /* Spoolss depends on a winreg pipe, so start it first. */ + if (!NT_STATUS_IS_OK(rpc_winreg_init(NULL))) { + exit(1); + } + + if (!NT_STATUS_IS_OK(rpc_spoolss_init(&spoolss_cb))) { + exit(1); + } + + static_init_rpc; + /* only start the background queue daemon if we are running as a daemon -- bad things will happen if smbd is launched via inetd and we fork a copy of @@ -1214,7 +1163,8 @@ extern void build_options(bool screen); if (is_daemon && !interactive && lp_parm_bool(-1, "smbd", "backgroundqueue", true)) { - start_background_queue(); + start_background_queue(smbd_event_context(), + smbd_messaging_context()); } if (!is_daemon) { @@ -1229,7 +1179,14 @@ extern void build_options(bool screen); /* close our standard file descriptors */ close_low_fds(False); /* Don't close stderr */ - smbd_process(); +#ifdef HAVE_ATEXIT + atexit(killkids); +#endif + + /* Stop zombies */ + smbd_setup_sig_chld_handler(); + + smbd_process(smbd_server_conn); exit_server_cleanly(NULL); return(0); @@ -1245,9 +1202,12 @@ extern void build_options(bool screen); exit_server("open_sockets_smbd() failed"); TALLOC_FREE(frame); + /* make sure we always have a valid stackframe */ + frame = talloc_stackframe(); smbd_parent_loop(parent); exit_server_cleanly(NULL); + TALLOC_FREE(frame); return(0); }