http://git-wip-us.apache.org/repos/asf/couchdb/blob/e62a4fc1/apps/couch/c_src/spawnkillable/couchspawnkillable_win.c ---------------------------------------------------------------------- diff --git a/apps/couch/c_src/spawnkillable/couchspawnkillable_win.c b/apps/couch/c_src/spawnkillable/couchspawnkillable_win.c new file mode 100644 index 0000000..0678231 --- /dev/null +++ b/apps/couch/c_src/spawnkillable/couchspawnkillable_win.c @@ -0,0 +1,145 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Do what 2 lines of shell script in couchspawnkillable does... +// * Create a new suspended process with the same (duplicated) standard +// handles as us. +// * Write a line to stdout, consisting of the path to ourselves, plus +// '--kill {pid}' where {pid} is the PID of the newly created process. +// * Un-suspend the new process. +// * Wait for the process to terminate. +// * Terminate with the child's exit-code. + +// Later, couch will call us with --kill and the PID, so we dutifully +// terminate the specified PID. + +#include <stdlib.h> +#include "windows.h" + +char *get_child_cmdline(int argc, char **argv) +{ + // make a new command-line, but skipping me. + // XXX - todo - spaces etc in args??? + int i; + char *p, *cmdline; + int nchars = 0; + int nthis = 1; + for (i=1;i<argc;i++) + nchars += strlen(argv[i])+1; + cmdline = p = malloc(nchars+1); + if (!cmdline) + return NULL; + for (i=1;i<argc;i++) { + nthis = strlen(argv[i]); + strncpy(p, argv[i], nthis); + p[nthis] = ' '; + p += nthis+1; + } + // Replace the last space we added above with a '\0' + cmdline[nchars-1] = '\0'; + return cmdline; +} + +// create the child process, returning 0, or the exit-code we will +// terminate with. +int create_child(int argc, char **argv, PROCESS_INFORMATION *pi) +{ + char buf[1024]; + DWORD dwcreate; + STARTUPINFO si; + char *cmdline; + if (argc < 2) + return 1; + cmdline = get_child_cmdline(argc, argv); + if (!cmdline) + return 2; + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + // depending on how *our* parent is started, we may or may not have + // a valid stderr stream - so although we try and duplicate it, only + // failing to duplicate stdin and stdout are considered fatal. + if (!DuplicateHandle(GetCurrentProcess(), + GetStdHandle(STD_INPUT_HANDLE), + GetCurrentProcess(), + &si.hStdInput, + 0, + TRUE, // inheritable + DUPLICATE_SAME_ACCESS) || + !DuplicateHandle(GetCurrentProcess(), + GetStdHandle(STD_OUTPUT_HANDLE), + GetCurrentProcess(), + &si.hStdOutput, + 0, + TRUE, // inheritable + DUPLICATE_SAME_ACCESS)) { + return 3; + } + DuplicateHandle(GetCurrentProcess(), + GetStdHandle(STD_ERROR_HANDLE), + GetCurrentProcess(), + &si.hStdError, + 0, + TRUE, // inheritable + DUPLICATE_SAME_ACCESS); + + si.dwFlags = STARTF_USESTDHANDLES; + dwcreate = CREATE_SUSPENDED; + if (!CreateProcess( NULL, cmdline, + NULL, + NULL, + TRUE, // inherit handles + dwcreate, + NULL, // environ + NULL, // cwd + &si, + pi)) + return 4; + return 0; +} + +// and here we go... +int main(int argc, char **argv) +{ + char out_buf[1024]; + int rc; + DWORD cbwritten; + DWORD exitcode; + PROCESS_INFORMATION pi; + if (argc==3 && strcmp(argv[1], "--kill")==0) { + HANDLE h = OpenProcess(PROCESS_TERMINATE, 0, atoi(argv[2])); + if (!h) + return 1; + if (!TerminateProcess(h, 0)) + return 2; + CloseHandle(h); + return 0; + } + // spawn the new suspended process + rc = create_child(argc, argv, &pi); + if (rc) + return rc; + // Write the 'terminate' command, which includes this PID, back to couch. + // *sob* - what about spaces etc? + sprintf_s(out_buf, sizeof(out_buf), "%s --kill %d\n", + argv[0], pi.dwProcessId); + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), out_buf, strlen(out_buf), + &cbwritten, NULL); + // Let the child process go... + ResumeThread(pi.hThread); + // Wait for the process to terminate so we can reflect the exit code + // back to couch. + WaitForSingleObject(pi.hProcess, INFINITE); + if (!GetExitCodeProcess(pi.hProcess, &exitcode)) + return 6; + return exitcode; +}
http://git-wip-us.apache.org/repos/asf/couchdb/blob/e62a4fc1/apps/couch/include/couch_db.hrl ---------------------------------------------------------------------- diff --git a/apps/couch/include/couch_db.hrl b/apps/couch/include/couch_db.hrl new file mode 100644 index 0000000..e0a1c82 --- /dev/null +++ b/apps/couch/include/couch_db.hrl @@ -0,0 +1,286 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-define(LOCAL_DOC_PREFIX, "_local/"). +-define(DESIGN_DOC_PREFIX0, "_design"). +-define(DESIGN_DOC_PREFIX, "_design/"). +-define(DEFAULT_COMPRESSION, snappy). + +-define(MIN_STR, <<"">>). +-define(MAX_STR, <<255>>). % illegal utf string + +% the lowest possible database sequence number +-define(LOWEST_SEQ, 0). + +-define(REWRITE_COUNT, couch_rewrite_count). + +-define(JSON_ENCODE(V), jiffy:encode(V, [uescape])). +-define(JSON_DECODE(V), couch_util:json_decode(V)). + +-define(b2l(V), binary_to_list(V)). +-define(l2b(V), list_to_binary(V)). +-define(term_to_bin(T), term_to_binary(T, [{minor_version, 1}])). +-define(term_size(T), + try + erlang:external_size(T) + catch _:_ -> + byte_size(?term_to_bin(T)) + end). + +-define(DEFAULT_ATTACHMENT_CONTENT_TYPE, <<"application/octet-stream">>). + +-define(LOG_DEBUG(Format, Args), + case couch_log:debug_on(?MODULE) of + true -> + couch_log:debug(Format, Args); + false -> ok + end). + +-define(LOG_INFO(Format, Args), + case couch_log:info_on(?MODULE) of + true -> + couch_log:info(Format, Args); + false -> ok + end). + +-define(LOG_WARN(Format, Args), + case couch_log:warn_on(?MODULE) of + true -> + couch_log:warn(Format, Args); + false -> ok + end). + +-define(LOG_ERROR(Format, Args), couch_log:error(Format, Args)). + +% Tree::term() is really a tree(), but we don't want to require R13B04 yet +-type branch() :: {Key::term(), Value::term(), Tree::term()}. +-type path() :: {Start::pos_integer(), branch()}. +-type tree() :: [branch()]. % sorted by key + +-record(rev_info, + { + rev, + seq = 0, + deleted = false, + body_sp = nil % stream pointer + }). + +-record(doc_info, + { + id = <<"">>, + high_seq = 0, + revs = [] % rev_info + }). + +-record(full_doc_info, + {id = <<"">>, + update_seq = 0, + deleted = false, + rev_tree = [], + leafs_size = 0 + }). + +-record(httpd, + {mochi_req, + peer, + method, + requested_path_parts, + path_parts, + db_url_handlers, + user_ctx, + req_body = undefined, + design_url_handlers, + auth, + default_fun, + url_handlers + }). + + +-record(doc, + { + id = <<"">>, + revs = {0, []}, + + % the json body object. + body = {[]}, + + atts = [], % attachments + + deleted = false, + + % key/value tuple of meta information, provided when using special options: + % couch_db:open_doc(Db, Id, Options). + meta = [] + }). + + +-record(att, + { + name, + type, + att_len, + disk_len, % length of the attachment in its identity form + % (that is, without a content encoding applied to it) + % differs from att_len when encoding /= identity + md5= <<>>, + revpos=0, + data, + encoding=identity % currently supported values are: + % identity, gzip + % additional values to support in the future: + % deflate, compress + }). + + +-record(user_ctx, + { + name=null, + roles=[], + handler + }). + +% This should be updated anytime a header change happens that requires more +% than filling in new defaults. +% +% As long the changes are limited to new header fields (with inline +% defaults) added to the end of the record, then there is no need to increment +% the disk revision number. +% +% if the disk revision is incremented, then new upgrade logic will need to be +% added to couch_db_updater:init_db. + +-define(LATEST_DISK_VERSION, 6). + +-record(db_header, + {disk_version = ?LATEST_DISK_VERSION, + update_seq = 0, + unused = 0, + fulldocinfo_by_id_btree_state = nil, + docinfo_by_seq_btree_state = nil, + local_docs_btree_state = nil, + purge_seq = 0, + purged_docs = nil, + security_ptr = nil, + revs_limit = 1000 + }). + +-record(db, + {main_pid = nil, + update_pid = nil, + compactor_pid = nil, + instance_start_time, % number of microsecs since jan 1 1970 as a binary string + fd, + updater_fd, + fd_ref_counter, + header = #db_header{}, + committed_update_seq, + fulldocinfo_by_id_btree, + docinfo_by_seq_btree, + local_docs_btree, + update_seq, + name, + filepath, + validate_doc_funs = [], + security = [], + security_ptr = nil, + user_ctx = #user_ctx{}, + waiting_delayed_commit = nil, + revs_limit = 1000, + fsync_options = [], + options = [], + compression, + before_doc_update = nil, % nil | fun(Doc, Db) -> NewDoc + after_doc_read = nil % nil | fun(Doc, Db) -> NewDoc + }). + + +-record(view_query_args, { + start_key, + end_key, + start_docid = ?MIN_STR, + end_docid = ?MAX_STR, + + direction = fwd, + inclusive_end=true, % aka a closed-interval + + limit = 10000000000, % Huge number to simplify logic + skip = 0, + + group_level = 0, + + view_type = nil, + include_docs = false, + doc_options = [], + conflicts = false, + stale = false, + multi_get = false, + callback = nil, + list = nil +}). + +-record(view_fold_helper_funs, { + reduce_count, + passed_end, + start_response, + send_row +}). + +-record(reduce_fold_helper_funs, { + start_response, + send_row +}). + +-record(extern_resp_args, { + code = 200, + stop = false, + data = <<>>, + ctype = "application/json", + headers = [], + json = nil +}). + +-record(index_header, + {seq=0, + purge_seq=0, + id_btree_state=nil, + view_states=nil + }). + +% small value used in revision trees to indicate the revision isn't stored +-define(REV_MISSING, []). + +-record(changes_args, { + feed = "normal", + dir = fwd, + since = 0, + limit = 1000000000000000, + style = main_only, + heartbeat, + timeout, + filter = "", + filter_fun, + filter_args = [], + include_docs = false, + doc_options = [], + conflicts = false, + db_open_options = [] +}). + +-record(btree, { + fd, + root, + extract_kv = fun({_Key, _Value} = KV) -> KV end, + assemble_kv = fun(Key, Value) -> {Key, Value} end, + less = fun(A, B) -> A < B end, + reduce = nil, + compression = ?DEFAULT_COMPRESSION +}). http://git-wip-us.apache.org/repos/asf/couchdb/blob/e62a4fc1/apps/couch/include/couch_js_functions.hrl ---------------------------------------------------------------------- diff --git a/apps/couch/include/couch_js_functions.hrl b/apps/couch/include/couch_js_functions.hrl new file mode 100644 index 0000000..a48feae --- /dev/null +++ b/apps/couch/include/couch_js_functions.hrl @@ -0,0 +1,170 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-define(AUTH_DB_DOC_VALIDATE_FUNCTION, <<" + function(newDoc, oldDoc, userCtx, secObj) { + if (newDoc._deleted === true) { + // allow deletes by admins and matching users + // without checking the other fields + if ((userCtx.roles.indexOf('_admin') !== -1) || + (userCtx.name == oldDoc.name)) { + return; + } else { + throw({forbidden: 'Only admins may delete other user docs.'}); + } + } + + if ((oldDoc && oldDoc.type !== 'user') || newDoc.type !== 'user') { + throw({forbidden : 'doc.type must be user'}); + } // we only allow user docs for now + + if (!newDoc.name) { + throw({forbidden: 'doc.name is required'}); + } + + if (!newDoc.roles) { + throw({forbidden: 'doc.roles must exist'}); + } + + if (!isArray(newDoc.roles)) { + throw({forbidden: 'doc.roles must be an array'}); + } + + for (var idx = 0; idx < newDoc.roles.length; idx++) { + if (typeof newDoc.roles[idx] !== 'string') { + throw({forbidden: 'doc.roles can only contain strings'}); + } + } + + if (newDoc._id !== ('org.couchdb.user:' + newDoc.name)) { + throw({ + forbidden: 'Doc ID must be of the form org.couchdb.user:name' + }); + } + + if (oldDoc) { // validate all updates + if (oldDoc.name !== newDoc.name) { + throw({forbidden: 'Usernames can not be changed.'}); + } + } + + if (newDoc.password_sha && !newDoc.salt) { + throw({ + forbidden: 'Users with password_sha must have a salt.' + + 'See /_utils/script/couch.js for example code.' + }); + } + + if (newDoc.password_scheme === \"pbkdf2\") { + if (typeof(newDoc.iterations) !== \"number\") { + throw({forbidden: \"iterations must be a number.\"}); + } + if (typeof(newDoc.derived_key) !== \"string\") { + throw({forbidden: \"derived_key must be a string.\"}); + } + } + + var is_server_or_database_admin = function(userCtx, secObj) { + // see if the user is a server admin + if(userCtx.roles.indexOf('_admin') !== -1) { + return true; // a server admin + } + + // see if the user a database admin specified by name + if(secObj && secObj.admins && secObj.admins.names) { + if(secObj.admins.names.indexOf(userCtx.name) !== -1) { + return true; // database admin + } + } + + // see if the user a database admin specified by role + if(secObj && secObj.admins && secObj.admins.roles) { + var db_roles = secObj.admins.roles; + for(var idx = 0; idx < userCtx.roles.length; idx++) { + var user_role = userCtx.roles[idx]; + if(db_roles.indexOf(user_role) !== -1) { + return true; // role matches! + } + } + } + + return false; // default to no admin + } + + if (!is_server_or_database_admin(userCtx, secObj)) { + if (oldDoc) { // validate non-admin updates + if (userCtx.name !== newDoc.name) { + throw({ + forbidden: 'You may only update your own user document.' + }); + } + // validate role updates + var oldRoles = oldDoc.roles.sort(); + var newRoles = newDoc.roles.sort(); + + if (oldRoles.length !== newRoles.length) { + throw({forbidden: 'Only _admin may edit roles'}); + } + + for (var i = 0; i < oldRoles.length; i++) { + if (oldRoles[i] !== newRoles[i]) { + throw({forbidden: 'Only _admin may edit roles'}); + } + } + } else if (newDoc.roles.length > 0) { + throw({forbidden: 'Only _admin may set roles'}); + } + } + + // no system roles in users db + for (var i = 0; i < newDoc.roles.length; i++) { + if (newDoc.roles[i][0] === '_') { + throw({ + forbidden: + 'No system roles (starting with underscore) in users db.' + }); + } + } + + // no system names as names + if (newDoc.name[0] === '_') { + throw({forbidden: 'Username may not start with underscore.'}); + } + + var badUserNameChars = [':']; + + for (var i = 0; i < badUserNameChars.length; i++) { + if (newDoc.name.indexOf(badUserNameChars[i]) >= 0) { + throw({forbidden: 'Character `' + badUserNameChars[i] + + '` is not allowed in usernames.'}); + } + } + } +">>). + + +-define(OAUTH_MAP_FUN, <<" + function(doc) { + if (doc.type === 'user' && doc.oauth && doc.oauth.consumer_keys) { + for (var consumer_key in doc.oauth.consumer_keys) { + for (var token in doc.oauth.tokens) { + var obj = { + 'consumer_secret': doc.oauth.consumer_keys[consumer_key], + 'token_secret': doc.oauth.tokens[token], + 'username': doc.name + }; + emit([consumer_key, token], obj); + } + } + } + } +">>).
