1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * $Id: protocoll.pike,v 1.8 2009/08/07 15:22:36 nicke Exp $
21 inherit Events.Listener;
22 #include <attributes.h>
27 #include <functions.h>
28 #include <attributes.h>
33 class protocoll : public binary,login{
40 //#define DEBUG_PROTOCOL
43 #define PROTO_LOG(s, args...) werror(s+"\n", args)
45 #define PROTO_LOG(s, args...)
48 void send_message(string str);
49 void close_connection();
50 void register_send_function(function f, function e);
53 mapping mCommandServer;
59 // slow command logging
60 int slow = _Server->get_config("log_slow_commands");
65 mapping mEvents = ([ ]);
68 class SocketListener {
71 bool mapEvents = false;
75 void create(int event, object obj, object socket, bool mapE, bool receiveSelf, string s)
77 ::create(event, PHASE_NOTIFY, obj, notify, oUser);
79 myEvents = receiveSelf;
84 void notify(int event, mixed args, object eventObj) {
86 object socket = this_socket();
88 if ( zero_type(::get_object()) ) {
89 destruct(this_object());
92 if ( mySocket == socket && !myEvents ) {
97 SEND_COAL(time(), COAL_EVENT, target->get_object_id(),
98 target->get_object_class(),
99 ({ event, eventObj->get_params(), session_id,
100 socket == mySocket }));
102 SEND_COAL(time(), COAL_EVENT, target->get_object_id(),
103 target->get_object_class(),
104 ({ event, args[1..], session_id,
105 socket == mySocket}));
114 * COAL_event is not used at all since there are usually
115 * no events coming from a client.
117 * @param int t_id - the transaction id
118 * @param object obj - the context object
119 * @param mixed args - parameter array
120 * @return ok or failed
123 COAL_event(int t_id, object obj, mixed args)
128 int COAL_getobject(int t_id, object obj, mixed args)
130 mapping attributes = obj->query_attributes();
132 // sends only attributes yet
133 send_message( coal_compose(t_id, COAL_SENDOBJECT,
134 obj->get_object_id(), obj->get_object_class(),
135 ({ attributes }) ) );
140 * COAL_command: Call a function inside steam. The args are
141 * an array with one or two parameters. The first on is the function
142 * to call and the second one is an array again containing all the
143 * parameters to be passed to the function call.
145 * @param int t_id - the transaction id
146 * @param object obj - the context object
147 * @param mixed args - parameter array
148 * @return ok or failed
151 COAL_command(int t_id, object obj, mixed args)
157 if ( !objectp(obj) ) return E_NOTEXIST | E_OBJECT;
158 if ( obj->status && obj->status() == PSTAT_DELETED ) return E_DELETED;
160 if ( sizeof(args) >= 2 )
161 [ cmd, args ] = args;
166 if ( functionp(obj->get_object) ) {
167 f = obj->find_function( cmd );
168 obj = obj->get_object();
170 else if ( !functionp( f = obj[cmd] ) )
173 THROW("Function: " + cmd + " not found inside ("+obj->get_object_id()+
174 ")", E_FUNCTION|E_NOTEXIST);
176 if ( !arrayp(args) ) args = ({ args });
178 int oid = obj->get_object_id();
179 int oclass = obj->get_object_class();
181 int tt = get_time_millis();
183 tt = get_time_millis() - tt;
184 if ( slow && tt > slow )
185 get_module("log")->log("slow_requests", LOG_LEVEL_INFO,
186 "%s Functioncall of %s in %O took %d ms",
187 timelib.event_time(time()), cmd, obj, tt);
189 if ( objectp(oUser) )
190 oUser->command_done(time());
193 functionp(res->is_async_return) &&
194 res->is_async_return() )
196 res->resultFunc = coal_send_result;
198 res->cmd = COAL_COMMAND;
200 res->oclass = oclass;
203 SEND_COAL(t_id, COAL_COMMAND, oid, oclass, res);
210 void coal_send_result(object ret, mixed res)
212 SEND_COAL(ret->tid, ret->cmd, ret->oid, ret->oclass, res);
218 * COAL_query_commands: returns a list of callable commands of the
221 * @param int t_id - the transaction id
222 * @param object obj - the context object
223 * @param mixed args - parameter array
224 * @return ok or failed
228 COAL_query_commands(int t_id, object obj, mixed args)
231 return E_NOTEXIST | E_OBJECT;
232 THROW("query_commands is unsupported", E_ERROR_PROTOCOL);
238 * Set the client features of this connection.
240 * @param int t_id - the transaction id
241 * @param object obj - the context object
242 * @param mixed args - parameter array
243 * @return ok or failed.
246 COAL_set_client(int t_id, object obj, mixed args)
248 if ( sizeof(args) != 1 || !intp(args[0]) )
249 return E_FORMAT | E_TYPE;
250 iClientFeatures = args[0];
251 SEND_COAL(t_id, COAL_SET_CLIENT, 0, 0, ({ }));
262 int COAL_ping(int t_id, object obj, mixed args)
264 SEND_COAL(t_id, COAL_PONG, 0, 0, ({ }));
268 int COAL_pong(int t_id, object obj, mixed args)
270 // clients are not supposed to send pongs
274 * Login the server with name and password. Optional the parameters client-name
275 * and features can be used to login. If no features and name is given the
276 * server will use "steam" and all client features. Otherwise the file client.h
277 * describes all possible features. Right now only CLIENT_FEATURES_EVENTS
278 * (enables the socket to get events) and CLIENT_FEATURES_MOVE (moves the
279 * user to his workroom when disconnecting and back to the last logout place
280 * when connecting). Apart from that the features can be checked at the user
281 * object by calling the function get_status(). It will return a bit vector
282 * of all set features. This enables clients to check if a user hears a chat
285 * @param t_id - id of the transfer
286 * @param obj_id - the relevant object
287 * @param args - the arguments, { user, password } optional two other
288 * parameters could be used: { user, password, client-name,
290 * @return ok or error code
292 * @see database.lookup_user
295 COAL_login(int t_id, object obj, mixed args)
298 string u_name, u_pass;
300 if ( sizeof(args) < 2 || !stringp(args[0]) || !stringp(args[1]) )
301 return E_FORMAT | E_TYPE;
303 u_name = args[0]; /* first argument is the username */
305 PROTO_LOG("login("+u_name+")");
306 sClientClass = CLIENT_CLASS_STEAM;
307 if ( sizeof(args) > 3 ) {
308 sClientClass = args[2];
309 if ( !intp(args[3]) )
310 THROW("Third argument is not an integer", E_TYPE);
311 iClientFeatures = args[3];
314 iClientFeatures = CLIENT_STATUS_CONNECTED;
316 if ( sizeof(args) == 5 )
319 mixed err = catch(uid = get_module("auth")->authenticate(u_name, u_pass));
322 FATAL("COAL: failed to authenticate: %O\n", err[0], err[1]);
323 return E_ACCESS | E_PASSWORD;
326 return E_ACCESS | E_PASSWORD;
328 if ( functionp(uid->is_async_return) && uid->is_async_return() ) {
329 uid->resultFunc = async_login_user;
333 do_login_user(uid, t_id);
339 void async_login_user(object async, object uid)
341 do_login_user(uid, async->tid);
347 void do_login_user(object uid, int t_id)
349 // allready connected to user - relogin
352 int last_login = login_user(uid);
353 object server = master()->get_server();
355 session_id = uid->get_session_id();
356 send_message( coal_compose(t_id, COAL_LOGIN, uid->get_object_id(),
357 uid->get_object_class(),
358 ({ uid->get_user_name(),
359 server->get_version(),
360 server->get_last_reboot(),
364 MODULE_OBJECTS->lookup("rootroom"),
365 MODULE_GROUPS->lookup("sTeam"),
366 _Server->get_modules(),
367 _Server->get_classes(),
368 _Server->get_configs(),
376 int COAL_hello(int t_id, object obj, mixed args)
380 if ( sizeof(args) < 2 || !stringp(args[0]) || !stringp(args[1]) )
381 return E_FORMAT | E_TYPE;
383 object cluster = get_module("Cluster");
384 if ( !objectp(cluster) )
385 steam_error("Standalone sTeam-Server !");
388 name = args[0]; /* first argument is the username */
390 object server = cluster->hello(name, cert);
391 if ( !objectp(server) )
392 steam_error("Unable to verify Authentication !");
393 login_user(server); // establish connection with server object
394 sClientClass = CLIENT_CLASS_SERVER;
395 send_message( coal_compose(t_id, COAL_LOGIN, 0,
397 ({ name, server->get_version(),
398 server->get_last_reboot(),
400 version(), _Database,
401 MODULE_OBJECTS->lookup("rootroom"),
402 MODULE_GROUPS->lookup("sTeam"),
403 _Server->get_modules(),
404 _Server->get_classes(),
405 _Server->get_configs(),
410 int COAL_relogin(int t_id, object obj, mixed args)
413 string u_name, u_pass;
416 if ( sizeof(args) < 2 || !stringp(args[0]) || !stringp(args[1]) )
417 return E_FORMAT | E_TYPE;
419 u_name = args[0]; /* first argument is the username */
421 sClientClass = CLIENT_CLASS_STEAM;
422 if ( sizeof(args) > 3 ) {
423 sClientClass = args[2];
424 if ( !intp(args[3]) )
425 THROW("Third argument is not an integer", E_TYPE);
426 iClientFeatures = args[3];
429 iClientFeatures = CLIENT_STATUS_CONNECTED;
431 if ( sizeof(args) == 5 )
434 uid = get_module("auth")->authenticate( u_name, u_pass );
437 return E_ACCESS | E_PASSWORD;
441 last_login = login_user(uid);
443 send_message( coal_compose(t_id, COAL_LOGIN, uid->get_object_id(),
444 uid->get_object_class(), ({ })) );
449 * called when logging out
451 * @param t_id - the current transaction id
452 * @param obj - the relevant object (not used in this case)
453 * @return ok - works all the time
457 COAL_logout(int t_id, object obj, mixed args)
459 PROTO_LOG("Logging out...\n");
468 * @param t_id - the transaction id of the command
469 * @param obj - the relevant object
470 * @param args - arguments for the download (ignored)
471 * @return error code or ok
475 COAL_file_download(int t_id, object obj, mixed args)
481 return E_NOTEXIST | E_OBJECT;
482 else if ( obj->get_content_size() == 0 ) {
483 SEND_COAL(t_id, COAL_FILE_UPLOAD, obj->get_object_id(),
484 obj->get_object_class(), ({ obj->get_content_size() }));
492 type = obj->query_attribute(DOC_MIME_TYPE);
493 PROTO_LOG("mime:"+type);
494 obj = obj->get_object();
496 if ( !functionp(obj->get_content_callback) ) {
498 if ( obj->get_object_class() & CLASS_CONTAINER ) {
500 index = obj->get_object_byname("index.html");
501 if ( !objectp(index) )
502 index = obj->get_object_byname("index.htm");
503 if ( !objectp(index) )
504 index = obj->get_object_byname("index.xml");
506 if ( !objectp(index) )
508 obj = index->get_object();
511 if ( sizeof(args) == 0 )
512 send = obj->get_content_callback( ([ "raw": 1, ]) );
514 send = obj->get_content_callback(args[0]);
516 PROTO_LOG("Now acknowledging download !");
517 SEND_COAL(t_id, COAL_FILE_UPLOAD, obj->get_object_id(),
518 obj->get_object_class(), ({ obj->get_content_size() }));
521 iTransfer = COAL_TRANSFER_SEND;
522 register_send_function(send, download_finished);
527 void receive_message(string str) { }
532 * download finished will set the mode back to no-transfer
534 * @see COAL_file_download
537 private void download_finished()
539 PROTO_LOG("transfer finished...");
540 iTransfer = COAL_TRANSFER_NONE;
549 * @param t_id - the transaction id of the command
550 * @param obj - the relevant object
551 * @param args - arguments for the upload (1 arg, url and size)
552 * @return error code or ok
553 * @see COAL_file_download
556 COAL_file_upload(int t_id, object obj, mixed args)
563 * find the object or create it...
565 if ( !arrayp(args) ||
566 (sizeof(args) != 1 && sizeof(args) != 2 && sizeof(args) != 3) )
568 return E_FORMAT | E_TYPE;
570 switch ( sizeof(args) ) {
572 [ path, url, size ] = args;
582 if ( objectp(path) ) {
583 obj = _FILEPATH->resolve_path(path, url);
586 obj = _FILEPATH->path_to_object(url);
588 if ( !objectp(obj) ) {
589 object factory, cont;
591 factory = _Server->get_factory(CLASS_DOCUMENT);
592 cont = _FILEPATH->path_to_environment(url);
593 obj = factory->execute((["url":url,]));
596 PROTO_LOG("object created="+master()->stupid_describe(obj,255));
599 PROTO_LOG("found object.="+master()->stupid_describe(obj,255));
601 if ( !functionp(obj->receive_content) )
602 return E_NOTEXIST | E_OBJECT;
603 PROTO_LOG("sending ok...");
604 SEND_COAL(t_id, COAL_FILE_DOWNLOAD, 0, 0, ({ obj }));
605 iTransfer = COAL_TRANSFER_RCV;
606 iTransferSize = size;
607 oTransfer = ((program)"/kernel/DocFile")(obj, "wct");
608 obj->set_attribute(DOC_LAST_ACCESSED, time());
609 obj->set_attribute(DOC_LAST_MODIFIED, time());
614 * COAL_upload_start - start an upload and
615 * call upload_package subsequently.
617 * @param t_id - the transaction id of the command
618 * @param obj - the relevant object
619 * @param args - arguments for the upload (1 arg, url and size)
620 * @return error code or ok
621 * @see COAL_file_download
624 COAL_upload_start(int t_id, object obj, mixed args)
628 /* find the object or create it... */
630 if ( !arrayp(args) || sizeof(args) != 1 )
631 return E_FORMAT | E_TYPE;
638 obj = _FILEPATH->path_to_object(url);
640 if ( !objectp(obj) ) {
641 object factory, cont;
643 factory = _Server->get_factory(CLASS_DOCUMENT);
644 if ( !objectp(factory) ) LOG("Unable to find document factory !\n");
645 cont = _FILEPATH->path_to_environment(url);
646 obj = factory->execute((["url":url,]));
647 PROTO_LOG("object created="+master()->stupid_describe(obj,255));
650 PROTO_LOG("found object.="+master()->stupid_describe(obj,255));
652 if ( !functionp(obj->receive_content) )
653 return E_NOTEXIST | E_OBJECT;
654 SEND_COAL(t_id, COAL_FILE_DOWNLOAD, 0, 0, ({ obj }) );
656 // only set upload function, but dont set transfer mode,
657 // this means the protocoll is not blocking anymore !
658 oTransfer = ((program)"/kernel/DocFile")(obj, "wct");
659 obj->set_attribute(DOC_LAST_ACCESSED, time());
660 obj->set_attribute(DOC_LAST_MODIFIED, time());
665 * Upload a package to steam. Before this command can be used
666 * there has to be a call to upload start before to define
667 * a callback function receiving the data.
669 * @param t_id - the transaction id of the command.
670 * @param obj - the relevant object.
671 * @param args - arguments for the query containing the content.
672 * @return ok or failed.
675 int COAL_upload_package(int t_id, object obj, mixed args)
677 if ( !objectp(oTransfer) )
678 THROW("No upload function - start upload with COAL_UPLOAD_START !",
680 PROTO_LOG("uploading...");
681 if ( sizeof(args) != 1 )
682 return E_FORMAT | E_TYPE;
683 PROTO_LOG("upload_package()");
684 if ( !stringp(args[0]) || args[0] == 0 ) {
688 PROTO_LOG("Finished upload !\n");
689 // at this point send back that we are finished, so client can logout
690 SEND_COAL(t_id, COAL_UPLOAD_FINISHED, 0, 0, ({ obj }));
693 PROTO_LOG("Received package: " + strlen(args[0]));
694 oTransfer->write(args[0]);
699 int COAL_log(int t_id, object obj, mixed args)
701 if ( sizeof(args) != 1 )
702 return E_FORMAT | E_TYPE;
705 int COAL_retr_log(int t_id, object obj, mixed args)
710 int add_event(object obj, int event, bool receiveSelf, bool mapEvents)
712 if ( !mappingp(mEvents[event]) )
713 mEvents[event] = ([ ]);
714 if ( objectp(mEvents[event][obj]) )
717 SocketListener l = SocketListener(event, obj, this_object(), mapEvents,
718 receiveSelf, session_id);
720 mEvents[event][obj] = l;
721 object listener = obj->listen_event(l);
722 if ( listener->get_listener_id() != l->get_listener_id() ) {
723 steam_error("Found previous listener !");
730 int COAL_subscribe(int t_id, object obj, mixed args)
739 if ( sizeof(args) != 1 && sizeof(args) != 2 )
740 return E_FORMAT | E_TYPE;
742 mixed events = args[0];
743 if ( sizeof(args) >= 2 )
745 if ( sizeof(args) >= 3 )
746 receiveSelf = args[2];
752 if ( !arrayp(events) ) {
753 int mask = events & 0xf0000000;
754 for ( i = 0; i < 28; i++ ) {
755 if ( (id = events & (1<<i)) > 0 )
756 new_events+=({ add_event(obj,id|mask,receiveSelf,mapEvents) });
760 for ( i = 0; i < sizeof(events); i++ )
761 new_events+=({ add_event(obj, events[i], receiveSelf, mapEvents)});
763 SEND_COAL(t_id, COAL_SUBSCRIBE, obj->get_object_id(),
764 obj->get_object_class(), ({ new_events }));
773 int COAL_unsubscribe(int t_id, object obj, mixed args)
775 int events_removed = 0;
783 PROTO_LOG("Unsubscribing events (%s) = %O", obj->describe(), events);
784 for ( int i = 0; i < sizeof(events); i++ ) {
785 if ( !mappingp(mEvents[events[i]]) )
787 object listener = mEvents[events[i]][obj];
788 if ( objectp(listener) ) {
789 object target = listener->get_object();
790 if ( objectp(target) )
791 target->ignore_event(listener);
793 m_delete(mEvents[events[i]], obj);
797 FATAL("Cannot remove listener for %d on %s", events[i],
800 SEND_COAL(t_id, COAL_UNSUBSCRIBE, obj->get_object_id(),
801 obj->get_object_class(), ({ events_removed }));
805 int COAL_reg_service(int t_id, object obj, mixed args)
807 if ( !objectp(obj) || obj == _Server )
808 obj = get_module("ServiceManager");
809 obj->register_service(send_service, notify_service, @args);
810 SEND_COAL(t_id, COAL_REG_SERVICE, obj->get_object_id(),
811 obj->get_object_class(), ({ }));
816 void send_service(mixed args)
818 catch(SEND_COAL(0, COAL_COMMAND, 0, 0, ({ "call_service", args })));
824 void notify_service(object event, string name)
826 mapping args = event->get_params();
828 catch(SEND_COAL(0, COAL_COMMAND, 0, 0, ({ "notify", args })));
836 * Initialize the protocoll.
843 COAL_EVENT: COAL_event,
844 COAL_COMMAND: COAL_command,
845 COAL_LOGIN: COAL_login,
846 COAL_LOGOUT: COAL_logout,
847 COAL_FILE_UPLOAD: COAL_file_upload,
848 COAL_FILE_DOWNLOAD: COAL_file_download,
849 COAL_SET_CLIENT: COAL_set_client,
850 COAL_UPLOAD_PACKAGE: COAL_upload_package,
851 COAL_UPLOAD_START: COAL_upload_start,
852 COAL_PING: COAL_ping,
853 COAL_PONG: COAL_pong,
855 COAL_RETR_LOG: COAL_retr_log,
856 COAL_SUBSCRIBE: COAL_subscribe,
857 COAL_UNSUBSCRIBE: COAL_unsubscribe,
858 COAL_REG_SERVICE: COAL_reg_service,
859 COAL_RELOGIN: COAL_relogin,
860 COAL_SERVERHELLO: COAL_hello,
861 COAL_GETOBJECT: COAL_getobject,
863 // any connection is guest user first!
864 login_user(_Persistence->lookup_user("guest"));
870 * send a message to the client - this function can only be called
871 * by the connected user-object
873 * @param tid - transaction id
874 * @param cmd - the command
875 * @param obj - the relevant object
876 * @param args - the arguments for the command
880 send_client_message(int tid, int cmd, object obj, mixed ... args)
882 if ( !is_user_object(CALLER) )
884 if ( tid == USE_LAST_TID )
886 SEND_COAL(tid, cmd, obj->get_object_id(), obj->get_object_class(), args);
890 * Compose a coal command by passing a number of parameters.
892 * @param int t_id - the transaction id
893 * @param int cmd - the coal command to call
894 * @param int o_id - the object id of the context object
895 * @param int class_id - the class of the context object
896 * @param mixed args - the parameters
897 * @return composed string
899 string coal_compose(int t_id, int cmd, int o_id, int class_id, mixed args)
901 return ::coal_compose(t_id, cmd, o_id, class_id, args);
908 foreach(indices(mEvents), int event)
909 if ( mappingp(mEvents[event]) )
910 foreach(values(mEvents[event]), object listener)