/* * Call backend plugin */ int from_client_validate(clicon_handle h, int s, struct clicon_msg *msg, const char *label) { char *dbname; int retval = -1; if (clicon_msg_validate_decode(msg, &dbname, label) < 0){ send_msg_err(s, clicon_errno, clicon_suberrno, clicon_err_reason); goto err; } clicon_debug(1, "Validate %s", dbname); if (candidate_validate(h, dbname, clicon_running_db(h)) < 0){ clicon_debug(1, "Validate %s failed", dbname); retval = 0; /* We ignore errors from commit, but maybe we should fail on fatal errors? */ goto err; } retval = 0; if (send_msg_ok(s) < 0) goto done; goto done; err: /* XXX: more elaborate errstring? */ if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0) retval = -1; done: unchunk_group(__FUNCTION__); return retval; } /* from_client_validate */
/*! Unload all plugins: call exit function and close shared handle * @param[in] h Clicon handle */ int clixon_plugin_exit(clicon_handle h) { clixon_plugin *cp; plgexit_t *exitfn; int i; char *error; for (i = 0; i < _clixon_nplugins; i++) { cp = &_clixon_plugins[i]; if ((exitfn = cp->cp_api.ca_exit) == NULL) continue; if (exitfn(h) < 0) { clicon_debug(1, "plugin_exit() failed"); return -1; } if (dlclose(cp->cp_handle) != 0) { error = (char*)dlerror(); clicon_err(OE_PLUGIN, errno, "dlclose: %s", error ? error : "Unknown error"); } } if (_clixon_plugins){ free(_clixon_plugins); _clixon_plugins = NULL; } _clixon_nplugins = 0; return 0; }
/* * \brief open a unix-domain socket and bind it to a file. * * The socket is accessed via CLICON_SOCK option, has 770 permissions * and group according to CLICON_SOCK_GROUP option. */ int config_socket_init(clicon_handle h) { int s; struct sockaddr_un addr; mode_t old_mask; char *config_sock; char *config_group; gid_t gid; struct stat st; /* first find sockpath and remove it if it exists (it shouldn't) */ if ((config_sock = clicon_sock(h)) == NULL) return -1; if (lstat(config_sock, &st) == 0 && unlink(config_sock) < 0){ clicon_err(OE_UNIX, errno, "%s: unlink(%s)", __FUNCTION__, config_sock); return -1; } /* then find configuration group (for clients) and find its groupid */ if ((config_group = clicon_sock_group(h)) == NULL) return -1; if (group_name2gid(config_group, &gid) < 0) return -1; #if 0 if (gid == 0) clicon_log(LOG_WARNING, "%s: No such group: %s\n", __FUNCTION__, config_group); #endif /* create unix socket */ if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { clicon_err(OE_UNIX, errno, "%s: socket", __FUNCTION__); return -1; } // setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one)); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, config_sock, sizeof(addr.sun_path)-1); old_mask = umask(S_IRWXO | S_IXGRP | S_IXUSR); if (bind(s, (struct sockaddr *)&addr, SUN_LEN(&addr)) < 0){ clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__); umask(old_mask); goto err; } umask(old_mask); /* change socket path file group */ if (lchown(config_sock, -1, gid) < 0){ clicon_err(OE_UNIX, errno, "%s: lchown(%s, %s)", __FUNCTION__, config_sock, config_group); goto err; } clicon_debug(1, "Listen on server socket at %s", addr.sun_path); if (listen(s, 5) < 0){ clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__); goto err; } return s; err: close(s); return -1; }
/* * from_client_commit * Handle an incoming commit message from a client. * XXX: If commit succeeds and snapshot/startup fails, we have strange state: * the commit has succeeded but an error message is returned. */ int from_client_commit(clicon_handle h, int s, struct clicon_msg *msg, const char *label) { char *candidate, *running; uint32_t snapshot, startup; int retval = -1; char *snapshot_0; if (clicon_msg_commit_decode(msg, &candidate, &running, &snapshot, &startup, label) < 0) goto err; if (candidate_commit(h, candidate, running) < 0){ clicon_debug(1, "Commit %s failed", candidate); retval = 0; /* We ignore errors from commit, but maybe we should fail on fatal errors? */ goto err; } clicon_debug(1, "Commit %s", candidate); if (snapshot && config_snapshot(clicon_dbspec_key(h), running, clicon_archive_dir(h)) < 0) goto err; if (startup){ snapshot_0 = chunk_sprintf(__FUNCTION__, "%s/0", clicon_archive_dir(h)); if (file_cp(snapshot_0, clicon_startup_config(h)) < 0){ clicon_err(OE_PROTO, errno, "%s: Error when creating startup", __FUNCTION__); goto err; } } retval = 0; if (send_msg_ok(s) < 0) goto done; goto done; err: /* XXX: more elaborate errstring? */ if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0) retval = -1; done: unchunk_group(__FUNCTION__); return retval; /* may be zero if we ignoring errors from commit */ } /* from_client_commit */
/* * Plugin initialization */ int plugin_init(clicon_handle h) { int i; char *key; int retval = -1; for (i = 0; resolv_conf_fmts[i].lm_key; i++) { key = resolv_conf_fmts[i].lm_key; if (dbdep(h, TRANS_CB_COMMIT, dns_commit, (void *)NULL, 1, key) == NULL) { clicon_debug(1, "Failed to create dependency '%s'", key); goto done; } clicon_debug(1, "Created dependency '%s'", key); } retval = 0; done: return retval; }
/*! Load a set of plugin objects from a directory and and call their init-function * @param[in] h Clicon handle * @param[in] function Which function symbol to load and call (eg CLIXON_PLUGIN_INIT) * @param[in] dir Directory. .so files in this dir will be loaded. * @param[in] regexp Regexp for matching files in plugin directory. Default *.so. * @retval 0 OK * @retval -1 Error */ int clixon_plugins_load(clicon_handle h, char *function, char *dir, char *regexp) { int retval = -1; int ndp; struct dirent *dp = NULL; int i; char filename[MAXPATHLEN]; clixon_plugin *cp; int ret; clicon_debug(1, "%s", __FUNCTION__); /* Get plugin objects names from plugin directory */ if((ndp = clicon_file_dirent(dir, &dp, regexp?regexp:"(.so)$", S_IFREG)) < 0) goto done; /* Load all plugins */ for (i = 0; i < ndp; i++) { snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", (int)strlen(filename), filename); if ((ret = plugin_load_one(h, filename, function, RTLD_NOW, &cp)) < 0) goto done; if (ret == 0) continue; _clixon_nplugins++; if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) { clicon_err(OE_UNIX, errno, "realloc"); goto done; } _clixon_plugins[_clixon_nplugins-1] = *cp; free(cp); } retval = 0; done: if (dp) free(dp); return retval; }
/* * Plugin initialization */ int plugin_init(clicon_handle h) { char *dir; int retval = -1; PyImport_AppendInittab("_cliconcli", MOD_INITFUNC(_cli)); Py_InitializeEx(0); /* Append application plugin directory */ if ((dir = clicon_option_str(h, "CLICON_CLI_DIR")) == NULL) { clicon_err(OE_PLUGIN, 0, "CLICON_CLI_DIR not set"); goto quit; } if (syspath_insert(dir) < 0) goto quit; /* Append CLICON python module directory */ if (syspath_insert(PYCLICON_MODDIR) < 0) goto quit; retval = plugin_call(h, "_plugin_init"); quit: if (retval <= 0) /* Error or no loaded plugins */ Py_Finalize(); if (debug && retval == 0) clicon_debug(1, "DEBUG: No python plugins loaded"); else if (retval > 0) clicon_debug(1, "DEBUG: Loaded %d python plugins", retval); return (retval < 0) ? -1 : 0; }
/*! Call plugin_start in all plugins * @param[in] h Clicon handle * Call plugin start functions (if defined) * @note Start functions used to have argc/argv. Use clicon_argv_get() instead */ int clixon_plugin_start(clicon_handle h) { clixon_plugin *cp; int i; plgstart_t *startfn; /* Plugin start */ for (i = 0; i < _clixon_nplugins; i++) { cp = &_clixon_plugins[i]; if ((startfn = cp->cp_api.ca_start) == NULL) continue; if (startfn(h) < 0) { clicon_debug(1, "plugin_start() failed"); return -1; } } return 0; }
/* * Call plugin_start in all plugins */ int netconf_plugin_start(clicon_handle h, int argc, char **argv) { int i; plgstart_t *startfn; for (i = 0; i < nplugins; i++) { /* Call exit function is it exists */ if ((startfn = dlsym(plugins[i], "plugin_start")) == NULL) break; optind = 0; if (startfn(h, argc, argv) < 0) { clicon_debug(1, "plugin_start() failed\n"); return -1; } } return 0; }
/* * netconf_plugin_load * Load allplugins you can find in CLICON_NETCONF_DIR */ int netconf_plugin_load(clicon_handle h) { int retval = -1; char *dir; int ndp; struct dirent *dp; int i; char *filename; plghndl_t *handle; if ((dir = clicon_netconf_dir(h)) == NULL) goto quit; /* Get plugin objects names from plugin directory */ if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__))<0) goto quit; /* Load all plugins */ for (i = 0; i < ndp; i++) { filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name); clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", (int)strlen(filename), filename); if (filename == NULL) { clicon_err(OE_UNIX, errno, "chunk"); goto quit; } if ((handle = plugin_load (h, filename, RTLD_NOW, __FUNCTION__)) == NULL) goto quit; if ((plugins = rechunk(plugins, (nplugins+1) * sizeof (*plugins), NULL)) == NULL) { clicon_err(OE_UNIX, errno, "chunk"); goto quit; } plugins[nplugins++] = handle; unchunk (filename); } retval = 0; quit: unchunk_group(__FUNCTION__); return retval; }
/*! Run the restconf user-defined credentials callback if present * Find first authentication callback and call that, then return. * The callback is to set the authenticated user * @param[in] h Clicon handle * @param[in] arg Argument, such as fastcgi handler for restconf * @retval -1 Error * @retval 0 Not authenticated * @retval 1 Authenticated * @note If authenticated either a callback was called and clicon_username_set() * Or no callback was found. */ int clixon_plugin_auth(clicon_handle h, void *arg) { clixon_plugin *cp; int i; plgauth_t *authfn; /* Plugin auth */ int retval = 1; for (i = 0; i < _clixon_nplugins; i++) { cp = &_clixon_plugins[i]; if ((authfn = cp->cp_api.ca_auth) == NULL) continue; if ((retval = authfn(h, arg)) < 0) { clicon_debug(1, "plugin_auth() failed"); return -1; } break; } return retval; }
/* * plugin_modify_key_value * A wrapper function for invoking the plugin dependency set/del call * for a changed a key value. * The routine logs on debug. * It also checks whether an error was properly registered using clicon_err(). * Arguments: * db: The database which contains the key value, most relevant in a set operation * but may possibly in some cases be important in delete operations, although * I cannot think of any,.. * key: The name of the key in the database above. * op: Either set or delete * dep: plugin dependency information. Contains function and argument pointers. * * Returns: * 0: OK * -1: An error occured in the plugin commit function. It is assumed that * clicon_err() has been called there. Here, we interpret the clicon_err * as a 'commit' error and does not handle it fatally. */ static int plugin_modify_key_value(clicon_handle h, char *db, char *key, enum trans_cb_type type, lv_op_t op, dbdep_t *dp) { int retval = -1; clicon_debug(2, "commit diff %c%s", (op==LV_SET)?'+':'-', key); clicon_err_reset(); if (dp->dp_callback(h, db, type, op, key, dp->dp_arg) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_err(OE_DB, 0, "Unknown error: %c%s: plugin does not make clicon_err call on error", (op==LV_SET)?'+':'-', key); goto done; } retval = 0; done: return retval; }
/*! Load a dynamic plugin object and call its init-function * @param[in] h Clicon handle * @param[in] file Which plugin to load * @param[in] function Which function symbol to load and call * @param[in] dlflags See man(3) dlopen * @param[out] cpp Clixon plugin structure (if retval is 1) * @retval 1 OK * @retval 0 Failed load, log, skip and continue with other plugins * @retval -1 Error * @see clixon_plugins_load Load all plugins */ static int plugin_load_one(clicon_handle h, char *file, char *function, int dlflags, clixon_plugin **cpp) { int retval = -1; char *error; void *handle = NULL; plginit2_t *initfn; clixon_plugin_api *api = NULL; clixon_plugin *cp = NULL; char *name; char *p; clicon_debug(1, "%s file:%s function:%s", __FUNCTION__, file, function); dlerror(); /* Clear any existing error */ if ((handle = dlopen(file, dlflags)) == NULL) { error = (char*)dlerror(); clicon_err(OE_PLUGIN, errno, "dlopen: %s", error ? error : "Unknown error"); goto done; } /* call plugin_init() if defined, eg CLIXON_PLUGIN_INIT or CLIXON_BACKEND_INIT */ if ((initfn = dlsym(handle, function)) == NULL){ clicon_err(OE_PLUGIN, errno, "Failed to find %s when loading clixon plugin %s", CLIXON_PLUGIN_INIT, file); goto done; } if ((error = (char*)dlerror()) != NULL) { clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error); goto done; } clicon_err_reset(); if ((api = initfn(h)) == NULL) { if (!clicon_errno){ /* if clicon_err() is not called then log and continue */ clicon_log(LOG_DEBUG, "Warning: failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file); retval = 0; goto done; } else{ clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file); goto done; } } /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */ if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){ clicon_err(OE_UNIX, errno, "malloc"); goto done; } memset(cp, 0, sizeof(struct clixon_plugin)); cp->cp_handle = handle; /* Extract string after last '/' in filename, if any */ name = strrchr(file, '/') ? strrchr(file, '/')+1 : file; /* strip extension, eg .so from name */ if ((p=strrchr(name, '.')) != NULL) *p = '\0'; /* Copy name to struct */ memcpy(cp->cp_name, name, strlen(name)+1); snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", (int)strlen(name), name); cp->cp_api = *api; clicon_debug(1, "%s", __FUNCTION__); if (cp){ *cpp = cp; cp = NULL; } retval = 1; done: if (retval != 1 && handle) dlclose(handle); if (cp) free(cp); return retval; }
/* * config_accept_client * XXX: credentials not properly implemented */ int config_accept_client(int fd, void *arg) { clicon_handle h = (clicon_handle)arg; int s; struct sockaddr_un from; socklen_t len; struct client_entry *ce; #ifdef DONT_WORK /* XXX HAVE_SYS_UCRED_H */ struct xucred credentials; /* FreeBSD. */ socklen_t clen; #elif defined(SO_PEERCRED) struct ucred credentials; /* Linux. */ socklen_t clen; #endif char *config_group; struct group *gr; char *mem; int i; len = sizeof(from); if ((s = accept(fd, (struct sockaddr*)&from, &len)) < 0){ clicon_err(OE_UNIX, errno, "%s: accept", __FUNCTION__); return -1; } #if defined(SO_PEERCRED) /* fill in the user data structure */ clen = sizeof(credentials); if(getsockopt(s, SOL_SOCKET, SO_PEERCRED/* XXX finns ej i freebsd*/, &credentials, &clen)){ clicon_err(OE_UNIX, errno, "%s: getsockopt", __FUNCTION__); return 1; } #endif if ((ce = ce_add(&ce_list, (struct sockaddr*)&from)) == NULL) return -1; #if defined(SO_PEERCRED) ce->ce_pid = credentials.pid; ce->ce_uid = credentials.uid; #endif ce->ce_handle = h; /* check credentials of caller (not properly implemented yet) */ if ((config_group = clicon_sock_group(h)) == NULL) return -1; if ((gr = getgrnam(config_group)) != NULL){ i = 0; /* one of mem should correspond to ce->ce_uid */ while ((mem = gr->gr_mem[i++]) != NULL) ; } #if 0 { /* XXX */ int ii; struct client_entry *c; for (c = ce_list, ii=0; c; c = c->ce_next, ii++); clicon_debug(1, "Open client socket (nr:%d pid:%d [Total: %d])", ce->ce_nr, ce->ce_pid, ii); } #endif ce->ce_s = s; /* * Here we register callbacks for actual data socket */ if (event_reg_fd(s, from_client, (void*)ce, "client socket") < 0) return -1; return 0; }
/*! Send internal netconf rpc from client to backend * @param[in] h CLICON handle * @param[in] msg Encoded message. Deallocate woth free * @param[out] xret Return value from backend as xml tree. Free w xml_free * @param[inout] sock0 If pointer exists, do not close socket to backend on success * and return it here. For keeping a notify socket open * @note sock0 is if connection should be persistent, like a notification/subscribe api * @note xret is populated with yangspec according to standard handle yangspec */ int clicon_rpc_msg(clicon_handle h, struct clicon_msg *msg, cxobj **xret0, int *sock0) { int retval = -1; char *sock; int port; char *retdata = NULL; cxobj *xret = NULL; yang_stmt *yspec; #ifdef RPC_USERNAME_ASSERT assert(strstr(msg->op_body, "username")!=NULL); /* XXX */ #endif clicon_debug(1, "%s request:%s", __FUNCTION__, msg->op_body); if ((sock = clicon_sock(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set"); goto done; } /* What to do if inet socket? */ switch (clicon_sock_family(h)){ case AF_UNIX: if (clicon_rpc_connect_unix(msg, sock, &retdata, sock0) < 0){ #if 0 if (errno == ESHUTDOWN) /* Maybe could reconnect on a higher layer, but lets fail loud and proud */ cligen_exiting_set(cli_cligen(h), 1); #endif goto done; } break; case AF_INET: if ((port = clicon_sock_port(h)) < 0){ clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set"); goto done; } if (port < 0){ clicon_err(OE_FATAL, 0, "CLICON_SOCK_PORT not set"); goto done; } if (clicon_rpc_connect_inet(msg, sock, port, &retdata, sock0) < 0) goto done; break; } clicon_debug(1, "%s retdata:%s", __FUNCTION__, retdata); if (retdata){ yspec = clicon_dbspec_yang(h); if (xml_parse_string(retdata, yspec, &xret) < 0) goto done; } if (xret0){ *xret0 = xret; xret = NULL; } retval = 0; done: if (retdata) free(retdata); if (xret) xml_free(xret); return retval; }