# HG changeset patch # User Nathan Phillip Brink # Date 2010-08-07 20:08:12 # Node ID c318109520cc1f9622f24c60b0a6cd9dcfc102a3 # Parent d73a30892ea03b83d72831833b85c3b89e4eb9b5 User authentication and some access checking. diff --git a/doc/architecture.txt b/doc/architecture.txt --- a/doc/architecture.txt +++ b/doc/architecture.txt @@ -58,6 +58,10 @@ Concepts: of actions a client may perform. - user: A user is an entity which is given access to a distren server. + - user identifiction: As users are per-server, a user shall be identified by the name of the + server he has an account on combined with his handle. A tilde prefixes the username to + differentiate this URL from a job's URL. + distren:///~ , like distren://ohnopub.net/~ohnobinki - file: There are different uses of files above and distributed rendering requires file distribution. - file identification: Every file mentioned above was at least in the context of a job. Thus, diff --git a/etc/distrencommon.conf b/etc/distrencommon.conf --- a/etc/distrencommon.conf +++ b/etc/distrencommon.conf @@ -28,6 +28,7 @@ server localhost { hostname = "localhost" username = "test" + password = "xyzzy123" method = "tcp" types = {"submit", "distribution", "master"} } diff --git a/src/client/libdistren.c b/src/client/libdistren.c --- a/src/client/libdistren.c +++ b/src/client/libdistren.c @@ -47,6 +47,9 @@ int distren_init(distren_t *handle) struct distren_request *req; void *data; + const char *username; + const char *pass; + if(!handle) return 1; @@ -64,6 +67,20 @@ int distren_init(distren_t *handle) return 1; } + tmp = remoteio_authinfo_get((*handle)->options->remoteio, + (*handle)->server, + &username, + &pass); + if(tmp + || !username + || !pass) + { + fprintf(stderr, "error: unable to find information necessary to connect to the server named ``%s'', please check your configuration file.\n", + (*handle)->server); + distren_init_cleanup(*handle); + return 1; + } + tmp = remoteio_open_server(&(*handle)->rem, (*handle)->options->remoteio, (remoteio_read_handle_func_t)&libdistren_remoteio_read_handle, @@ -90,9 +107,40 @@ int distren_init(distren_t *handle) distren_request_send((*handle)->rem, req, data); distren_request_free_with_data(req, data); + tmp = distren_request_pass(&req, &data, username, pass); + if(tmp) + { + fprintf(stderr, "error: unable to allocate request"); + + distren_init_cleanup(*handle); + return 1; + } + distren_request_send((*handle)->rem, req, data); + distren_request_free_with_data(req, data); + + /* + * There is no response to the DISTREN_REQUEST_PASS packet. However, + * we want to ensure that the username/password we sent are valid + * before returning to the claler. Thus, we here send a PING + * packet. If we get the DISTREN_REQUEST_PONG, we know we're + * authenticated. Otherwise, we'll receive a quit/discconect packet. + */ + tmp = distren_request_poing(&req, &data, 1, "auth test", strlen("auth test")); + if(tmp) + { + fprintf(stderr, "error: Unable to allocate a DISTREN_REQUEST_PING\n"); + + distren_init_cleanup(*handle); + return 1; + } + distren_request_send((*handle)->rem, req, data); + distren_request_free_with_data(req, data); + + /* flush out the above packets. */ while((*handle)->rem - && (*handle)->state == DISTREN_STATE_VERSION) - multiio_poll((*handle)->multiio, 500); + && ((*handle)->state == DISTREN_STATE_VERSION + || (*handle)->state == DISTREN_STATE_AUTH)) + multiio_poll((*handle)->multiio, 500); if(!(*handle)->rem) { diff --git a/src/client/libdistren.h b/src/client/libdistren.h --- a/src/client/libdistren.h +++ b/src/client/libdistren.h @@ -43,7 +43,6 @@ enum distren_state */ DISTREN_STATE_AUTH, DISTREN_STATE_NORMAL, - DISTREN_STATE_UPLOADING, }; struct distren diff --git a/src/client/libdistren_request.c b/src/client/libdistren_request.c --- a/src/client/libdistren_request.c +++ b/src/client/libdistren_request.c @@ -25,7 +25,10 @@ #include "common/remoteio.h" #include "common/request.h" +#include + static void handle_ping(struct remoteio *rem, struct distren_request *req, void *req_data); +static void handle_pong(distren_t distren, struct remoteio *rem, struct distren_request *req, void *req_data); static void handle_version(distren_t distren, struct distren_request *req, void *req_data); static void handle_disconnect(distren_t distren, struct remoteio *rem, struct distren_request *req, void *req_data); @@ -72,6 +75,10 @@ size_t libdistren_remoteio_read_handle(s handle_ping(rem, req, req_data); break; + case DISTREN_REQUEST_PONG: + handle_pong(distren, rem, req, req_data); + break; + case DISTREN_REQUEST_VERSION: handle_version(distren, req, req_data); break; @@ -118,6 +125,15 @@ static void handle_ping(struct remoteio distren_request_free_with_data(pong_req, pong_req_data); } +static void handle_pong(distren_t distren, struct remoteio *rem, struct distren_request *req, void *req_data) +{ + const char *auth_test_str = "auth test"; + + if(req->len == strlen(auth_test_str) + && !strncmp(req_data, auth_test_str, req->len)) + distren->state = DISTREN_STATE_NORMAL; +} + static void handle_version(distren_t distren, struct distren_request *req, void *req_data) { struct distren_request_version version; diff --git a/src/common/libremoteio.h b/src/common/libremoteio.h --- a/src/common/libremoteio.h +++ b/src/common/libremoteio.h @@ -49,6 +49,7 @@ struct remoteio_server char *name; /*< The friendly named passed to remoteio_open() */ char *hostname; char *username; + char *password; enum remoteio_method method; unsigned int types; /*< See ``Server types'' in protocol.h */ }; diff --git a/src/common/options.c b/src/common/options.c --- a/src/common/options.c +++ b/src/common/options.c @@ -173,6 +173,7 @@ int options_init(int argc, char *argv[], cfg_opt_t server_opts[] = { CFG_STR("username", NULL, CFGF_NONE), + CFG_STR("password", NULL, CFGF_NONE), CFG_STR("hostname", NULL, CFGF_NONE), CFG_STR("method", "ssh", CFGF_NONE), CFG_STR_LIST("types", NULL, CFGF_NONE), diff --git a/src/common/protocol.h b/src/common/protocol.h --- a/src/common/protocol.h +++ b/src/common/protocol.h @@ -61,12 +61,28 @@ enum distren_request_type */ DISTREN_REQUEST_VERSION = 1, /** + * Test if the end has a live server. + * + * Only authenticated clients may use this request. Thus, if a + * client wants to confirm that a DISTREN_REQUEST_PASS request was + * successful, that client may send a DISTREN_REQUEST_PING + * immediately after the DISTREN_REQUEST_PASS and then wait for + * the PONG. + * * DATA: up to 32 bytes of a PING cookie * * REQUIRED: ALL */ DISTREN_REQUEST_PING = 2, /** + * Response to DISTREN_REQUEST_PING. + * + * Unauthenticated clients may be penalized for responding to PING + * requests. This is because a newly connecting client should + * queue a DISTREN_REQUEST_VERSION and DISTREN_REQUEST_PASS + * back-to-back before checking for and processing data from the + * remote server. + * * DATA: up to 32 bytes copied from a received PING cookie * * REQUIRED: ALL @@ -84,12 +100,31 @@ enum distren_request_type */ DISTREN_REQUEST_DISCONNECT = 4, + + /** + * Allow a client to identify itself using simple password + * authentication. + * + * As there is no distren request which affirms a + * DISTREN_REQUEST_PASS went through, clients may send a + * DISTREN_REQUEST_PING and wait for the DISTREN_REQUETS_PONG they + * want to block until they're authenticated. + * + * DATA: struct distren_request_pass + * + * REQUIRED: DISTREN_SERVERTYPE_SUBMIT (for now, since + * server2server links are only protected using password + * authentication, all server types have to support this except + * for the client.) + */ + DISTREN_REQUEST_PASS = 5, + /** * DATA: struct distren_request_submit * * REQUIRED: DISTREN_SERVERTYPE_SUBMIT */ - DISTREN_REQUEST_SUBMIT = 5, + DISTREN_REQUEST_SUBMIT = 6, /** * Inform the other party about a job. @@ -98,7 +133,7 @@ enum distren_request_type * * REQUIRED: ALL */ - DISTREN_REQUEST_JOBINFO = 6, + DISTREN_REQUEST_JOBINFO = 7, /** * Request a DISTREN_REQUEST_JOBINFO @@ -107,7 +142,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE */ - DISTREN_REQUEST_JOBINFO_GET = 7, + DISTREN_REQUEST_JOBINFO_GET = 8, /** * Command the other party to render a frame @@ -116,7 +151,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_RENDER */ - DISTREN_REQUEST_FRAME_RENDER = 8, + DISTREN_REQUEST_FRAME_RENDER = 9, /** * Inform the receiver of the sender's state, such as frames being * rendered or jobs that need to be completed. @@ -125,7 +160,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_RENDER, DISTREN_SERVERTYPE_DISTRIBUTE, DISTREN_SERVERTYPE_CLIENT */ - DISTREN_REQUEST_STATUS = 9, + DISTREN_REQUEST_STATUS = 10, /** * Request that the receiver send a DISTREN_REQUEST_FRAME_STATUS @@ -136,7 +171,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_RENDER */ - DISTREN_REQUEST_STATUS_GET = 10, + DISTREN_REQUEST_STATUS_GET = 11, /** * Declare that a client is preparing to post a file using a @@ -157,7 +192,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE */ - DISTREN_REQUEST_FILE_POST_START = 11, + DISTREN_REQUEST_FILE_POST_START = 12, /** * Allow a client to upload a job tarball over a remoteio line. A @@ -182,7 +217,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE */ - DISTREN_REQUEST_FILE_POST = 12, + DISTREN_REQUEST_FILE_POST = 13, /** * Marks a post-id's file as having completely uploaded. Provides @@ -193,7 +228,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE */ - DISTREN_REQUEST_FILE_POST_FINISH = 13, + DISTREN_REQUEST_FILE_POST_FINISH = 14, /** * Request information about obtaining a file (such as a @@ -203,7 +238,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_DISTRIBUTE */ - DISTREN_REQUEST_FILE_FIND = 14, + DISTREN_REQUEST_FILE_FIND = 15, /** * Provide information about obtaining a file (such as a URL). @@ -212,7 +247,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_DISTRIBUTE */ - DISTREN_REQUEST_FILE = 15, + DISTREN_REQUEST_FILE = 16, }; struct distren_request @@ -236,6 +271,14 @@ struct distren_request_version char package_string[DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN + 1]; }; +#define DISTREN_REQUEST_PASS_USERNAME_LEN (16) +#define DISTREN_REQUEST_PASS_PASS_LEN (32) +struct distren_request_pass +{ + char username[DISTREN_REQUEST_PASS_USERNAME_LEN]; + char pass[DISTREN_REQUEST_PASS_PASS_LEN]; +}; + #define DISTREN_REQUEST_FILE_POST_NAME_LEN (64) struct distren_request_file_post_start { diff --git a/src/common/remoteio.c b/src/common/remoteio.c --- a/src/common/remoteio.c +++ b/src/common/remoteio.c @@ -141,10 +141,17 @@ int remoteio_config(cfg_t *cfg, struct r char *method; cfg_aserver = cfg_getnsec(cfg, "server", counter); + + aserver.name = NULL; + aserver.hostname = NULL; + aserver.username = NULL; + aserver.password = NULL; aserver.name = strdup(cfg_title(cfg_aserver)); aserver.hostname = strdup(cfg_getstr(cfg_aserver, "hostname")); aserver.username = strdup(cfg_getstr(cfg_aserver, "username")); + if(cfg_getstr(cfg_aserver, "password")) + aserver.password = strdup(cfg_getstr(cfg_aserver, "password")); aserver.method = REMOTEIO_METHOD_MAX; method = cfg_getstr(cfg_aserver, "method"); @@ -288,6 +295,26 @@ int remoteio_open_server(struct remoteio return 0; } +int remoteio_authinfo_get(struct remoteio_opts *rem_opts, const char *servername, const char **username, const char **pass) +{ + struct remoteio_server *server; + + *username = NULL; + *pass = NULL; + + server = remoteio_getserver(rem_opts, servername); + if(!server) + { + fprintf(stderr, "%s:%d: Could not find server named ``%s''\n", __FILE__, __LINE__, servername); + return 1; + } + + *username = server->username; + *pass = server->password; + + return 0; +} + /** * Implementation of multiio_event_handler_func_t */ diff --git a/src/common/remoteio.h b/src/common/remoteio.h --- a/src/common/remoteio.h +++ b/src/common/remoteio.h @@ -120,6 +120,21 @@ int remoteio_open_socket(struct remoteio int remoteio_write(struct remoteio *rem, const void *buf, size_t len); /** + * \brief Retrieves authentication information associated with a + * server's configuration entry. + * + * Possibly, the whole idea of remoteio handling server names with the + * remoteio_open() function should be moved somewhere else and + * remoteio should be more general-purpose I/O? + * + * \param rem_opts A remoteio options handle. + * \param servername The name of the server whose information should be retrieved. + * \param username Where to store a pointer to the username. Do not free this. + * \param pass Where to store a pointer to the password. Do not free this. + */ +int remoteio_authinfo_get(struct remoteio_opts *rem_opts, const char *servername, const char **username, const char **pass); + +/** * Closes a remoteio session. * * It is safe to call this function from within diff --git a/src/common/request.c b/src/common/request.c --- a/src/common/request.c +++ b/src/common/request.c @@ -69,6 +69,35 @@ int distren_request_parse_version(struct return 0; } +int distren_request_pass(struct distren_request **req, void **data, const char *username, const char *pass) +{ + struct distren_request_pass *request_pass; + + distren_request_new(req, sizeof(struct distren_request_pass), DISTREN_REQUEST_PASS); + request_pass = malloc(sizeof(struct distren_request_pass)); + if(!request_pass || !*req) + { + if(*req) + distren_request_free(*req); + free(request_pass); + + return 1; + } + + /* + * The packet itself doesn't need the string's NULL terminator + * _unless_ if the string is shorter than the maximum length of the + * username or password. Thus, strncpy()'s behavior (not strlcpy()'s + * behavior) is _exactly_ what we want for this case. + */ + strncpy(request_pass->username, username, DISTREN_REQUEST_PASS_USERNAME_LEN); + strncpy(request_pass->pass, pass, DISTREN_REQUEST_PASS_PASS_LEN); + + *data = request_pass; + + return 0; +} + int distren_request_poing(struct distren_request **req, void **data, short is_ping, const void *poing_cookie, size_t poing_data_len) { enum distren_request_type type; diff --git a/src/common/request.h b/src/common/request.h --- a/src/common/request.h +++ b/src/common/request.h @@ -56,6 +56,16 @@ int distren_request_version(struct distr int distren_request_parse_version(struct distren_request *req, void *data, struct distren_request_version *version); /** + * Initialize a PASS request so as to identify to a server. + * + * \param req Where to store the newly allocated req. + * \param data Where to store the newly allocated data. + * \param user The username to send to the server. + * \param pass The password to send to the server. + */ +int distren_request_pass(struct distren_request **req, void **data, const char *username, const char *pass); + +/** * Initialize a PING or PONG request. * * @param data a place to allocate storage for the data associated with this request diff --git a/src/server/distrend.c b/src/server/distrend.c --- a/src/server/distrend.c +++ b/src/server/distrend.c @@ -24,8 +24,9 @@ #include "distrenjob.h" #include "listen.h" +#include "mysql.h" #include "slavefuncs.h" -#include "mysql.h" +#include "user_mgr.h" #include "common/asprintf.h" #include "common/execio.h" @@ -62,6 +63,12 @@ struct general_info { /** general_info.xml */ char *geninfo; + + /** + * \brief Where to store the user listing. + */ + char *userlist; + /** * where to store in-progress uploads or things that should * otherwise be on the same filesystem as the rest of the datadir @@ -81,6 +88,8 @@ struct general_info time_t timestamp; unsigned long total_render_power; unsigned long total_priority_pieces; + + user_mgr_t user_mgr; }; @@ -98,6 +107,7 @@ int distrend_handle_request(struct distr * client request handlers */ int distrend_handle_version(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); +int distrend_handle_pass(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); int distrend_handle_file_post_start(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); int distrend_handle_file_post(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); int distrend_handle_file_post_finish(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); @@ -171,6 +181,18 @@ Ethan Zonca \n\ general_info.config->datadir); distren_mkdir_recurse(general_info.files.tmpdir); + _distren_asprintf(&general_info.files.userlist, "%s/users.xml", + general_info.config->datadir); + + /** configuraton stuff that depends on the paths being calculated, such as loading data */ + general_info.user_mgr = user_mgr_init(general_info.files.userlist); + if(!general_info.user_mgr) + { + fprintf(stderr, "Error initializing user_mgr\n"); + return 1; + } + + /** MySQL Connection */ fprintf(stderr,"Connecting to mysql...\n"); if(mysqlConnect(&general_info.conn, @@ -206,10 +228,12 @@ Ethan Zonca \n\ } } - distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_VERSION, &distrend_handle_version); - distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_START, &distrend_handle_file_post_start); - distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST, &distrend_handle_file_post); - distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_FINISH, &distrend_handle_file_post_finish); + distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_VERSION, &distrend_handle_version, (uint8_t)DISTREND_CLIENT_PREVERSION); + distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_PASS, + &distrend_handle_pass, (uint8_t)DISTREND_CLIENT_PREAUTH); + distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_START, &distrend_handle_file_post_start, (uint8_t)DISTREND_CLIENT_GOOD); + distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST, &distrend_handle_file_post, (uint8_t)DISTREND_CLIENT_GOOD); + distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_FINISH, &distrend_handle_file_post_finish, (uint8_t)DISTREND_CLIENT_GOOD); /* Main Loop */ general_info.config->die = 0; @@ -230,6 +254,8 @@ Ethan Zonca \n\ /** free() paths */ free(general_info.files.geninfo); + free(general_info.files.tmpdir); + free(general_info.files.userlist); mysqlDisconnect(general_info.conn); return 0; @@ -285,6 +311,45 @@ int distrend_handle_version(struct gener } /** + * Handle a DISTREN_REQUEST_PASS request. + */ +int distrend_handle_pass(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data) +{ + struct distren_request_pass *pass_req; + + char username[DISTREN_REQUEST_PASS_USERNAME_LEN + 1]; + char pass[DISTREN_REQUEST_PASS_PASS_LEN + 1]; + + struct user *user; + + if(req->len < sizeof(struct distren_request_pass)) + { + distrend_send_disconnect(client, "You tried to send too short of a DISTREN_REQUEST_PASS."); + return 1; + } + + pass_req = req_data; + memcpy(username, pass_req->username, DISTREN_REQUEST_PASS_USERNAME_LEN); + username[DISTREN_REQUEST_PASS_USERNAME_LEN] = '\0'; + + memcpy(pass, pass_req->pass, DISTREN_REQUEST_PASS_PASS_LEN); + pass[DISTREN_REQUEST_PASS_PASS_LEN] = '\0'; + + user = user_find(geninfo->user_mgr, username); + if(!user + || strcmp(user->username, username) + || strcmp(user->pass, pass)) + { + distrend_send_disconnect(client, "Invalid username or password."); + return 1; + } + + client->state = DISTREND_CLIENT_GOOD; + + return 0; +} + +/** * Traversal helper for distrend_client_find_post(). */ int distrend_client_find_post_traverse(uint32_t *post_id, struct distrend_client_file_post *client_file_post) diff --git a/src/server/listen.c b/src/server/listen.c --- a/src/server/listen.c +++ b/src/server/listen.c @@ -42,6 +42,11 @@ struct distrend_request_handler_info { enum distren_request_type request_type; distrend_handle_request_func_t handler; + /** + * A bitmasking of different enum distren_client_states which are + * allowed to use this particular command. + */ + uint8_t client_states; }; struct distrend_client *distrend_client_new(struct distrend_listens *listens, @@ -277,7 +282,6 @@ int listen_handle_accept(multiio_context short revent, struct distrend_listens *listens, int *port) - //int distrend_accept(struct distrend_listens *listens)//, struct distrend_clientset *clients, distrend_handle_request_t handlereq, void *handlereqdata) { struct distrend_client *newclient; @@ -361,6 +365,12 @@ size_t distrend_listen_read_handle(struc size_t used_len; + /* + * ignore packets from a client who's going to be disconnected. + */ + if(client->state == DISTREND_CLIENT_BAD) + return len; + used_len = 0; /** * Manage input, etc. @@ -555,7 +565,7 @@ int distrend_send_disconnect(struct dist return 0; } -int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler) +int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler, uint8_t client_state_mask) { struct distrend_request_handler_info *handler_info; @@ -565,6 +575,7 @@ int distrend_listen_handler_add(struct d handler_info->request_type = type; handler_info->handler = handler; + handler_info->client_states = client_state_mask; list_insert_after(listens->request_handlers, handler_info, 0); return 0; @@ -584,7 +595,19 @@ struct distrend_dispatch_request_data int _distrend_dispatch_request_trav(struct distrend_dispatch_request_data *data, struct distrend_request_handler_info *handler_info) { if(handler_info->request_type == data->req->type) - (*handler_info->handler)(data->geninfo, data->client, data->req, data->req_data); + { + /* check permissions first */ + if(!(data->client->state & handler_info->client_states)) + { + distrend_send_disconnect(data->client, "You attempted to use a command out of context."); + return FALSE; + } + + (*handler_info->handler)(data->geninfo, data->client, data->req, data->req_data); + + /* shortcut one a hit ;-) */ + return FALSE; + } return TRUE; } diff --git a/src/server/listen.h b/src/server/listen.h --- a/src/server/listen.h +++ b/src/server/listen.h @@ -59,27 +59,31 @@ struct distrend_client; */ #define DISTREND_LISTEN_DISCONNECT_GRACE 8 +/** + * Numbers are explicitly given so that commands can be restricted to + * clients in certain states. + */ enum distrend_client_state { /** The client hasn't yet given us its version. */ - DISTREND_CLIENT_PREVERSION, + DISTREND_CLIENT_PREVERSION = 1, /** We don't yet know the client. It may only use authentication commands. */ - DISTREND_CLIENT_PREAUTH, + DISTREND_CLIENT_PREAUTH = 2, /** The client is authenticated, etc. */ - DISTREND_CLIENT_GOOD, + DISTREND_CLIENT_GOOD = 4, /** The client is queued to be disconnected. (This state exists so that the client at least has a chance to recieve its disconnect message/error before being dumped). */ - DISTREND_CLIENT_BAD, + DISTREND_CLIENT_BAD = 8, }; struct distrend_listens @@ -188,11 +192,15 @@ int distrend_listen_add(struct distrend_ /** * Register a request handler with the listener. * - * @param config distrend's configuration - * @param type the request type for which this handler should be called - * @param handler the handler to call when a request of type type is received. + * \param config distrend's configuration. + * \param type The request type for which this handler should be + * called + * \param handler The handler to call when a request of type type is + * received. + * \param client_state_mask A mask of enum distrend_client_state + * values that are allowed to use this command. */ -int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler); +int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler, uint8_t client_state_mask); /** * cleans listening sockets/frees main struct. Unnecessary for a working server, currently a stub. diff --git a/src/server/tabletennis.c b/src/server/tabletennis.c --- a/src/server/tabletennis.c +++ b/src/server/tabletennis.c @@ -60,8 +60,8 @@ tabletennis_t tabletennis_new(struct dis tabletennis->clients_need_pong = q_init(); clock_gettime(CLOCK_MONOTONIC, &tabletennis->time_last_check); - distrend_listen_handler_add(listens, DISTREN_REQUEST_PING, &tabletennis_ping_request_handle); - distrend_listen_handler_add(listens, DISTREN_REQUEST_PONG, &tabletennis_pong_request_handle); + distrend_listen_handler_add(listens, DISTREN_REQUEST_PING, &tabletennis_ping_request_handle, (uint8_t)DISTREND_CLIENT_GOOD); + distrend_listen_handler_add(listens, DISTREN_REQUEST_PONG, &tabletennis_pong_request_handle, (uint8_t)DISTREND_CLIENT_GOOD); return tabletennis; } diff --git a/src/server/user_mgr.c b/src/server/user_mgr.c --- a/src/server/user_mgr.c +++ b/src/server/user_mgr.c @@ -1,21 +1,21 @@ /* - Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matthew Orlando - - This file is a part of DistRen. - - DistRen is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - DistRen 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with DistRen. If not, see . -*/ + * Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matthew Orlando + * + * This file is a part of DistRen. + * + * DistRen is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DistRen 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DistRen. If not, see . + */ #include "common/config.h" @@ -23,6 +23,7 @@ #include "common/asprintf.h" +#include #include #include @@ -37,305 +38,288 @@ #include #include -struct user_mgr_info +struct user_mgr { - struct user *user_array; - int current_users; - int user_array_size; -} user_mgr_info; - -int resize_user_array() -{ - int counter; - int counter2; - - // create array twice the size of the current amount of users - user_mgr_info.user_array_size = user_mgr_info.current_users * 2; - struct user *new_user_array = malloc(sizeof(struct user) * user_mgr_info.user_array_size); + /* items are of type user_t */ + list_t user_list; + /* where to load/save the userlist */ + char *userlist_filename; +}; - // this copies the original user_array over to the new one - // using two counters allows the array to be resized at any time - // leaving exactly 1 open space between each user when it is done; - counter2 = 0; - for(counter = 0; counter < user_mgr_info.current_users; counter++) - { - if(user_mgr_info.user_array[counter].username != 0) - { - new_user_array[counter2*2] = user_mgr_info.user_array[counter]; - counter2++; - } - } +static int user_find_traverse_forw(char *search_username, user_t user) +{ + if(strcmp(search_username, user->username) >= 0) + return FALSE; + return TRUE; +} - // cleanup old array - free(user_mgr_info.user_array); - - // change the pointer to point to the new user array - user_mgr_info.user_array = new_user_array; - - return 1; +static int user_find_traverse_back(char *search_username, user_t user) +{ + if(strcmp(search_username, user->username) <= 0) + return FALSE; + return TRUE; } -struct user *findUser(char *nameOfUser) +user_t user_find(user_mgr_t user_mgr, const char *username) { - int high; - int low; - int middle; - int result; + int list_direction; + list_traverse_func_t traverse_func; + + user_t user; + char *username_copy; - high = user_mgr_info.user_array_size - 1; - low = 0; - result = -1; + if(list_empty(user_mgr->user_list)) + return NULL; + + /* grab the current user for tiny optimizations.. */ + user = list_curr(user_mgr->user_list); + if(!user) + return NULL; - for(middle = (low+high)/2; 1 == 1; middle = (low+high)/2) - { - // in case middle lands on a part of the array with no user - while(user_mgr_info.user_array[middle].username == 0) - { - if(result < 0) - middle --; - else - middle ++; - } + list_direction = LIST_FORW; + traverse_func = (list_traverse_func_t)&user_find_traverse_forw; + if(strcmp(user->username, username) < 0) + { + list_direction = LIST_BACK; + traverse_func = (list_traverse_func_t)&user_find_traverse_back; + }; - // this is where the array is cut in half and the half that the nameOfUser is on is kept - result = strcmp(nameOfUser, user_mgr_info.user_array[middle].username); - if(result == 0) - return &user_mgr_info.user_array[middle]; - else if(result < 0) - high = middle; - else - low = middle; + username_copy = strdup(username); + list_traverse(user_mgr->user_list, username_copy, traverse_func, list_direction|LIST_CURR|LIST_ALTR); + free(username_copy); + user = list_curr(user_mgr->user_list); + if(!user) + return NULL; - // in case the user doesn't exist - if(high-low <= 0) - return 0; - } + if(!strcmp(username, user->username)) + return user; + + return NULL; } -int deleteUser(struct user *user_ptr) +int user_add(user_mgr_t user_mgr, const char *username, const char *pass) { - free(user_ptr->username); - memset(user_ptr, '\0', sizeof(struct user)); + user_t user; + short insert_after; - user_mgr_info.current_users--; - - return 1; - - backup_list_XML(); -} + user = user_find(user_mgr, username); + if(user) + return 1; -int createUser(struct user *user_ptr, char *nameOfUser) -{ - // clear old memory - memset(user_ptr, '\0', sizeof(struct user)); + /* + * The list should be positioned within one element of where we want + * to insert username. + */ + insert_after = 1; + user = list_curr(user_mgr->user_list); + if(user) + { + if(strcmp(username, user->username) < 0) + insert_after = 0; + } - user_ptr->username = nameOfUser; - user_ptr->render_power = 0; - user_ptr->last_job = 0; + /* + * construct the new user. + */ + user = malloc(sizeof(struct user)); + if(!user) + return 1; + user->username = strdup(username); + user->pass = strdup(pass); + user->render_power = 0; + user->last_job = 0; - user_mgr_info.current_users++; + /* I admit it... I'm completely crazy --binki */ + if(insert_after) + list_insert_after(user_mgr->user_list, user, 0); + else + list_insert_before(user_mgr->user_list, user, 0); - return 1; + return 0; } -// places the new user at position index in the array, moves other users if it needs to -int placeUser(int index, char *nameOfUser) +/** + * \brief For list_free() et al + */ +static void user_free(user_t user) { - int higher; - int lower; - int total_moves; - - total_moves = 0; + free(user->username); + free(user->pass); + free(user); +} - // I shift data in the array to create an open the space where the user should be added - // but first I figure out which way is the shortest - if(user_mgr_info.user_array[index].username != 0) - { - higher = index + 1; - while(user_mgr_info.user_array[higher].username != 0) - higher++; - - lower = index - 1; - while(user_mgr_info.user_array[lower].username != 0) - lower--; +int user_delete(user_mgr_t user_mgr, user_t user) +{ + user_t user_found; + int ret; - // here the data is shifted to open up a space - if(index - lower < higher - index) - { - total_moves = index - lower; - for(; lower < index; lower++) - memcpy(&user_mgr_info.user_array[lower], &user_mgr_info.user_array[lower + 1], sizeof(struct user)); - } - else - { - total_moves = higher - index; - for(; higher > index; higher--) - memcpy(&user_mgr_info.user_array[higher], &user_mgr_info.user_array[higher - 1], sizeof(struct user)); - } - } + ret = 0; - // add the user to the array - createUser(&user_mgr_info.user_array[index], nameOfUser); + user_found = user_find(user_mgr, user->username); + if(user_found + && user_found == user + && user == list_curr(user_mgr->user_list)) + list_remove_curr(user_mgr->user_list); + else + { + fprintf(stderr, __FILE__ ":%d:user_delete(): List is inconsistent :-/\n", __LINE__); + ret = 1; + } - if(total_moves > 50) - resize_user_array(); + user_free(user); - return 1; + return ret; } -int addUser(char *nameOfUser) +static int user_mgr_save_traverse(xmlTextWriterPtr writer, user_t user) { - int high; - int low; - int middle; - int result; - - high = user_mgr_info.user_array_size - 1; - low = 0; - result = -1; + char *tmp; - for(middle = (low+high)/2; 1 == 1; middle = (low+high)/2) - { - // in case middle lands on a part of the array with no user - while(user_mgr_info.user_array[middle].username == 0) - { - if(result < 0) - middle--; - else - middle++; - } + xmlTextWriterStartElement(writer, (xmlChar *)"user"); + + xmlTextWriterWriteAttribute(writer, (xmlChar *)"name", (xmlChar*)user->username); + xmlTextWriterWriteAttribute(writer, (xmlChar *)"pass", (xmlChar*)user->pass); - // this is where the array is cut in half and the half that the nameOfUser is on is kept - result = strcmp(nameOfUser, user_mgr_info.user_array[middle].username); - if(result == 0) - return 0; - else if(result < 0) - high = middle; - else - low = middle; - - // once there are less than 10 possible places for the user to be placed - // break out of this loop and begin next loop - if(high-low <= 10) - break; - } + _distren_asprintf(&tmp, "%d", user->last_job); + xmlTextWriterWriteAttribute(writer, (xmlChar *)"last_job", (xmlChar*)tmp); + free(tmp); - // this function starts at the low index number, and goes up until it finds a - // username that is bigger than it alphabetically, and tells the userPlacer - // that it needs to go 1 before that spot - for(; low <= high; low++) - { - while(user_mgr_info.user_array[low].username == 0) - low++; + _distren_asprintf(&tmp, "%d", user->render_power); + xmlTextWriterWriteAttribute(writer, (xmlChar *)"render_power", (xmlChar*)tmp); + free(tmp); - result = strcmp(nameOfUser, user_mgr_info.user_array[low].username) < 0; - if(result < 0) - { - placeUser(low - 1, nameOfUser); - return 1; - } - if(result == 0) - { - fprintf(stderr, "user already exists"); - return 0; - } - } + xmlTextWriterEndElement(writer); - backup_list_XML(); - return 0; + return TRUE; } -int initialize_users() +int user_mgr_save(user_mgr_t user_mgr, const char *filename) { - struct stat buffer; - // cif user_list.xml exists - if(stat("user_list.xml", &buffer) == 0) - { - restart_From_XML_backup(); - } - else - { - user_mgr_info.current_users = 0; + xmlTextWriterPtr writer; + + if(!filename) + filename = user_mgr->userlist_filename; + + writer = xmlNewTextWriterFilename(filename, 0); + xmlTextWriterStartDocument(writer, NULL, "utf-8", NULL); - user_mgr_info.user_array_size = 50; - user_mgr_info.user_array = malloc(sizeof(struct user) * 50); - } + /* + * create root element user_list + */ + xmlTextWriterStartElement(writer, (xmlChar*)"user_list"); - // if XML file is not found create new array of size 50 + /** + * \todo error checking/handling? + */ + list_traverse(user_mgr->user_list, writer, (list_traverse_func_t)&user_mgr_save_traverse, LIST_FRNT|LIST_FORW|LIST_SAVE); + xmlTextWriterEndElement(writer); + xmlTextWriterEndDocument(writer); + xmlTextWriterFlush(writer); - return 1; + return 0; } -/********************************** XMLness *****************************/ - -int backup_list_XML() +user_mgr_t user_mgr_init(const char *userfile) { - xmlTextWriterPtr writer; - char *tmp; - int counter; - - - writer = xmlNewTextWriterFilename("user_list.xml", 0); - xmlTextWriterStartDocument(writer, NULL, "utf-8", NULL); - - // create root element user_list - xmlTextWriterStartElement(writer, (xmlChar*)"user_list"); - - _distren_asprintf(&tmp, "%d", user_mgr_info.current_users); - xmlTextWriterWriteAttribute(writer, (xmlChar*)"amount_of_users", (xmlChar*)tmp); - free(tmp); - - for(counter = 0; counter < user_mgr_info.user_array_size; counter++) - { - if(user_mgr_info.user_array[counter].username != 0) - { - xmlTextWriterStartElement(writer, (xmlChar*)"user"); - - xmlTextWriterWriteAttribute(writer, (xmlChar*)"name", (xmlChar*)user_mgr_info.user_array[counter].username); - - _distren_asprintf(&tmp, "%d", user_mgr_info.user_array[counter].last_job); - xmlTextWriterWriteAttribute(writer, (xmlChar*)"last_job", (xmlChar*)tmp); - free(tmp); - - _distren_asprintf(&tmp, "%d", user_mgr_info.user_array[counter].render_power); - xmlTextWriterWriteAttribute(writer, (xmlChar*)"render_power", (xmlChar*)tmp); - free(tmp); - - xmlTextWriterEndElement(writer); - } - } - - return 0; - -} - -int restart_From_XML_backup(){ xmlDocPtr doc; xmlNodePtr cur; - int counter; + + user_mgr_t user_mgr; + + user_t user; + xmlChar *username; + xmlChar *pass; + xmlChar *tmp; + int render_power; + int last_job; - doc = xmlParseFile("user_list.xml"); + user_mgr = malloc(sizeof(struct user_mgr)); + user_mgr->user_list = list_init(); + user_mgr->userlist_filename = strdup(userfile); + + doc = xmlParseFile(userfile); + if (!doc) + { + fprintf(stderr, "user_mgr: Error opening userlist, assuming we should start with an empty userlist\n"); + return user_mgr; + } + cur = xmlDocGetRootElement(doc); if (xmlStrcmp(cur->name, (xmlChar*)"user_list")) { - fprintf(stderr, "xml document is wrong type"); + fprintf(stderr, "user_mgr: xml document is wrong type. Using empty userlist, which will overwrite the old userlist soon probably..."); xmlFreeDoc(doc); - return 1; + return user_mgr; } - user_mgr_info.current_users = atoi((char*)xmlGetProp(cur, (xmlChar*)"amount_of_users")); + for(cur = cur->xmlChildrenNode; cur; cur = cur->next) + { + if (cur->type != XML_ELEMENT_NODE) + /* skip the implicit XML_TEXT_NODEs */ + continue; + + if (xmlStrcmp(cur->name, (xmlChar *)"user")) + { + fprintf(stderr, "user_mgr: Unrecognized XML element: <%s />\n", + cur->name); + + continue; + } - user_mgr_info.user_array_size = user_mgr_info.current_users * 2; - user_mgr_info.user_array = malloc(sizeof(struct user) * user_mgr_info.user_array_size); + username = xmlGetProp(cur, (xmlChar *)"name"); + pass = xmlGetProp(cur, (xmlChar *)"pass"); + + if(!username) + { + fprintf(stderr, " is missing a name attribute! (skipping)\n"); + continue; + } + if(!pass) + { + fprintf(stderr, " is missing a pass attribute! (skipping)\n", + username ? (char *)username : ""); + continue; + } - cur = cur->xmlChildrenNode; - for(counter = 0; cur->next; counter++){ - user_mgr_info.user_array[counter*2].username = (char*)xmlGetProp(cur, (xmlChar*)"amount_of_users"); - user_mgr_info.user_array[counter*2].last_job = atoi((char*)xmlGetProp(cur, (xmlChar*)"last_job")); - user_mgr_info.user_array[counter*2].render_power = atoi((char*)xmlGetProp(cur, (xmlChar*)"render_power")); - cur = cur->next; - } + last_job = 0; + tmp = xmlGetProp(cur, (xmlChar *)"last_job"); + if(tmp) + { + last_job = atoi((char *)tmp); + xmlFree(tmp); + } + + render_power = 0; + if(tmp) + { + tmp = xmlGetProp(cur, (xmlChar *)"render_power"); + render_power = atoi((char *)tmp); + xmlFree(tmp); + } + + user_add(user_mgr, (char *)username, (char *)pass); + xmlFree(username); + xmlFree(pass); - return 0; + /* + * user_find should be a very inexpensive operation immediately + * after the user_add() above. I just don't want to trust to + * list_curr() right in this function ;-). + * + * Also, we set this information without passing the following + * as arguments to user_add() because user_add() is an external + * API used for when a user is initially added to the + * userlist. Thus, everybody'd be calling it with + * user_add("username", "pass", 0, 0); + */ + user = user_find(user_mgr, (char *)username); + if(user) + { + user->render_power = render_power; + user->last_job = last_job; + } + } + + return user_mgr; } diff --git a/src/server/user_mgr.h b/src/server/user_mgr.h --- a/src/server/user_mgr.h +++ b/src/server/user_mgr.h @@ -27,13 +27,63 @@ struct user { - char *username; - int render_power; - int last_job; + char *username; + char *pass; + int render_power; + int last_job; }; +typedef struct user *user_t; + +struct user_mgr; +typedef struct user_mgr *user_mgr_t; + +/** + * \brief Allocate and initialize a user_mgr. + * + * \param userfile The path to where and XML file with user + * information may be found and where the users file should be save + * to when new users are added. + * \return The new user_mgr or NULL on error. + */ +user_mgr_t user_mgr_init(const char *userfile); + +/** + * \brief Find a user by username. + * + * \param user_mgr The user_mgr... + * \param username The username to search for. + * \return The user if it is in user_mgr or NULL. + */ +user_t user_find(user_mgr_t user_mgr, const char *username); + +/** + * \brief Add a user to the user_mgr. + * + * \param username The user's username (which is local in scope to this server). + * \param pass The user's plaintext password (insecurity is sooo much easier ;-) ). + * \return 0 on success, 1 on failure (attempt to insert duplicate user, etc.). + */ +int user_add(user_mgr_t user_mgr, const char *username, const char *pass); + +/** + * \brief Delete a user. + * + * The user handle passed to this function is no longer valid after + * this function is called. + * + * \param user The user's handle as retrieved via user_find(). + * \return 0 on success, 1 on failure (user does not exist, inconsistency, etc.) + */ +int user_delete(user_mgr_t user_mgr, user_t user); + +/** + * \brief Write out the XML file listing all of the users local to this server. + * + * \param filename The file to write the userlist to or NULL if you + * want to write to the same filename you loaded the usre_mgr from. + */ +int user_mgr_save(user_mgr_t user_mgr, const char *filename); -int restart_From_XML_backup(); -int backup_list_XML(); #endif