/* Do not remove the headers from this file! see /USAGE for more info. */ /* ** socket.c ** ** This object represents an open UDP/TCP socket using the MudOS ** socket facilities. ** ** 09-Feb-95. Deathblade. Created. ** 05-Jan-96. Cowl. Added STREAM BINARY connect and listen styles ** 12-Jul-96. Rust. Added write callback. */ #include <mudlib.h> #include <socket.h> #include <driver/socket_err.h> #include <log.h> //#define SKTLOG(x,y) write_file("/open/sktlog",sprintf("%s: %O\n",x,y)) #define SKTLOG(x,y) /* ** If this is defined, then the specified privilege is needed to create ** an outbound connection */ #define REQUIRE_PRIV "Mudlib:socket" nosave private int style; nosave private int fdOwned = -1; /* no socket yet */ nosave private function read_func; nosave private function close_func; nosave private function write_func; nosave private mixed * write_queue = ({ }); nosave private int blocked; /* For debug purposes only */ nosave private mixed addr; void set_write_callback(function f) { write_func = f; } int stat_me() { switch ( style ) { case SKT_STYLE_LISTEN: printf("%O: listening at %O\n", this_object(), addr); printf(" read_func=%O close_func=%O\n", read_func, close_func); break; case SKT_STYLE_CONNECT: printf("%O: connected to %O\n", this_object(), socket_address(fdOwned)); printf(" read_func=%O close_func=%O\n", read_func, close_func); break; case SKT_STYLE_UDP: printf("%O: UDP at %O\n", this_object(), socket_address(fdOwned)); printf(" read_func=%O\n", read_func); break; case SKT_STYLE_LISTEN_M: printf("%O: (mud) listening at %O\n", this_object(), addr); printf(" read_func=%O close_func=%O\n", read_func, close_func); break; case SKT_STYLE_CONNECT_M: printf("%O: (mud) connected to %O\n", this_object(), socket_address(fdOwned)); printf(" read_func=%O close_func=%O\n", read_func, close_func); break; case SKT_STYLE_INT_ACQUIRE: printf("%O: accepted connection from %s\n", this_object(), socket_address(fdOwned)); printf(" read_func=%O close_func=%O\n", read_func, close_func); break; } if ( sizeof(write_queue) ) printf("queue: %O\n", write_queue); return 1; } //### socket_connect() doesn't take funcptr yet... /* private */ protected nomask void read_callback(int fd, mixed message) { SKTLOG("read_callback: self",this_object()); SKTLOG("read_callback: fd",fd); catch(evaluate(read_func, this_object(), message)); } private nomask void read_udp_callback(int fd, mixed message, string address) { SKTLOG("read_udp_callback: self",this_object()); SKTLOG("read_udp_callback: fd",fd); SKTLOG("read_udp_callback: read_func",read_func); catch(evaluate(read_func, this_object(), message, address)); } private nomask void close_callback(int fd) { SKTLOG("close_callback: self",this_object()); SKTLOG("close_callback: fd",fd); SKTLOG("close_callback: close_func",close_func); /* this descriptor is closed. don't try to close again. */ fdOwned = -1; if ( close_func ) { catch(evaluate(close_func, this_object())); } destruct(); } //### socket_connect() doesn't take a funcptr yet /* private */ protected nomask void write_callback(int fd) { mixed tmp; SKTLOG("write_callback: self",this_object()); SKTLOG("write_callback: fd",fd); SKTLOG("write_callback: # elem",sizeof(write_queue)); /* ** No longer blocked (can accept new data). */ if ( !sizeof(write_queue) && write_func && blocked ) { // write_queue = ({ evaluate(write_func, this_object()) }); evaluate(write_func, this_object()); } blocked = 0; while ( sizeof(write_queue) > 0 ) { int err; err = socket_write(fd, write_queue[0]); if ( err == EEALREADY ) { // write_callback will get called automatically. blocked = 1; return; } if ( err == EEWOULDBLOCK ) { // write_callback needs to get called manually. blocked = 1; call_out("write_callback",1,fd); return; } /* ** Remove the item from the queue. It has been written. */ write_queue = write_queue[1..]; if ( err == EECALLBACK ) { /* done for now... wait for the next callback */ blocked = 1; return; } if ( err < 0 ) { error("could not write: " + socket_error(err) + "\n"); } else if ( write_func ) { tmp = evaluate(write_func, this_object()); if ( sizeof(tmp) ) write_queue += ({ tmp }); } } } /* private */ nomask void release_callback(int fdToAcquire) { int err; SKTLOG("release_callback: self",this_object()); fdOwned = fdToAcquire; SKTLOG("release_callback: fdOwned",fdOwned); err = socket_acquire(fdOwned, (: read_callback :), (: write_callback :), (: close_callback :)); SKTLOG("release_callback: err",err); if ( err < 0 ) error("could not release: " + socket_error(err) + "\n"); /* ** Deliver a 0 indicating a new connection (and providing self) */ catch(evaluate(read_func, this_object(), 0)); } //### socket_listen doesn't take funcptrs yet... /* private */ protected nomask void listen_callback(int fd) { object s; int err; SKTLOG("listen_callback: self",this_object()); SKTLOG("listen_callback: fd",fd); fd = socket_accept(fd, (: read_callback :), (: write_callback :)); s = new(SOCKET, SKT_STYLE_INT_ACQUIRE, read_func, close_func); SKTLOG("listen_callback: new sock",s); err = socket_release(fd, s, "release_callback"); SKTLOG("listen_callback: err",err); if ( err < 0 ) error("could not release: " + socket_error(err) + "\n"); } //### need a way to protect this from random writes varargs nomask void send(mixed message, string address) { int err; SKTLOG("send: self",this_object()); SKTLOG("send: fd",fdOwned); SKTLOG("send: # elem",sizeof(write_queue)); if ( address ) err = socket_write(fdOwned, message, address); else if ( blocked ) { /* ** If we are blocked, then the socket doesn't want us to send ** any more. Place it on our queue for sending later. */ write_queue += ({ message }); } else { while ( sizeof(message) ) { err = socket_write(fdOwned, message); if ( err == EEALREADY ) { // write_callback will get called automatically. blocked = 1; write_queue += ({ message }); return; } if ( err == EEWOULDBLOCK ) { // write_callback needs to get called manually. blocked = 1; write_queue += ({ message }); call_out("write_callback", 1, fdOwned); return; } if ( err == EECALLBACK ) { /* ** Socket took the message but is blocked until it can ** write it out. Set a flag so that we don't write any ** more until we get the callback. */ blocked = 1; return; } message = ""; if ( write_func ) { message = evaluate(write_func, this_object()); } } } if ( err < 0 ) error("could not write: " + socket_error(err) + "\n"); } void remove() { int err; if ( fdOwned >= 0 ) { SKTLOG("remove: self",this_object()); SKTLOG("remove: fdOwned",fdOwned); err = socket_close(fdOwned); SKTLOG("remove: err",err); if ( err < 0 ) LOG_D->log(LOG_SOCKET, "could not close: " + socket_error(err) + "\n"); } destruct(); } nomask mixed *address() { string tmp; string host; int port; tmp = socket_address(fdOwned); sscanf(tmp, "%s %d", host, port); return ({ host, port }); } nomask int local_port() { string address; int port; sscanf(socket_address(fdOwned, 1), "%s %d", address, port); return port; } nomask string local_address() { string address; int port; sscanf(socket_address(fdOwned,1),"%s %d",address,port); return address; } void create(int skt_style, mixed p1, mixed p2, mixed p3) { int err; if ( !clonep() ) return; SKTLOG("create: self",this_object()); style = skt_style; // addr = p1; switch ( style ) { case SKT_STYLE_LISTEN: read_func = p2; close_func = p3; fdOwned = socket_create(1 /* STREAM */, (: read_callback :), (: close_callback :)); if ( fdOwned < 0 ) error("could not create socket: " + socket_error(fdOwned) + "\n"); if ( (err = socket_bind(fdOwned, p1)) < 0 ) error("could not bind socket: " + socket_error(err) + "\n"); if ( (err = socket_listen(fdOwned, "listen_callback")) < 0 ) error("could not listen to socket: " + socket_error(err) + "\n"); SKTLOG("create: SKT_STYLE_LISTEN",fdOwned); break; case SKT_STYLE_CONNECT: read_func = p2; close_func = p3; #ifdef REQUIRE_PRIV if ( !check_previous_privilege(REQUIRE_PRIV) ) { error("Insufficient privs to open an outgoing socket.\n"); } #endif fdOwned = socket_create(1 /* STREAM */, (: read_callback :), (: close_callback :)); if ( fdOwned < 0 ) error("could not create socket: " + socket_error(fdOwned) + "\n"); err = socket_connect(fdOwned, p1, "read_callback", "write_callback"); if ( err < 0 ) error("could not listen to socket: " + socket_error(err) + "\n"); SKTLOG("create: SKT_STYLE_CONNECT",fdOwned); SKTLOG("create: close_func",close_func); break; case SKT_STYLE_LISTEN_B: read_func = p2; close_func = p3; fdOwned = socket_create(3 /* STREAM BINARY */, (: read_callback :), (: close_callback :)); if ( fdOwned < 0 ) error("could not create socket: " + socket_error(fdOwned) + "\n"); if ( (err = socket_bind(fdOwned, p1)) < 0 ) error("could not bind socket: " + socket_error(err) + "\n"); if ( (err = socket_listen(fdOwned, "listen_callback")) < 0 ) error("could not listen to socket: " + socket_error(err) + "\n"); SKTLOG("create: SKT_STYLE_LISTEN_B",fdOwned); break; case SKT_STYLE_CONNECT_B: #ifdef REQUIRE_PRIV if ( !check_previous_privilege(REQUIRE_PRIV) ) { error("Insufficient privs to open an outgoing socket.\n"); } #endif read_func = p2; close_func = p3; fdOwned = socket_create(3 /* STREAM BINARY */, (: read_callback :), (: close_callback :)); if ( fdOwned < 0 ) error("could not create socket: " + socket_error(fdOwned) + "\n"); err = socket_connect(fdOwned, p1, "read_callback", "write_callback"); if ( err < 0 ) error("could not listen to socket: " + socket_error(err) + "\n"); SKTLOG("create: SKT_STYLE_CONNECT_B",fdOwned); SKTLOG("create: close_func",close_func); break; case SKT_STYLE_UDP: #ifdef REQUIRE_PRIV if ( !check_previous_privilege(REQUIRE_PRIV) ) { error("Insufficient privs to open a datagram socket.\n"); } #endif read_func = p2; fdOwned = socket_create(2 /* DATAGRAM */, (: read_udp_callback :)); if ( fdOwned < 0 ) error("could not create socket: " + socket_error(fdOwned) + "\n"); if ( (err = socket_bind(fdOwned, p1)) < 0 ) error("could not bind socket: " + socket_error(err) + "\n"); SKTLOG("create: SKT_STYLE_UDP",fdOwned); break; case SKT_STYLE_LISTEN_M: read_func = p2; close_func = p3; fdOwned = socket_create(0 /* MUD */, (: read_callback :), (: close_callback :)); if ( fdOwned < 0 ) error("could not create socket: " + socket_error(fdOwned) + "\n"); if ( (err = socket_bind(fdOwned, p1)) < 0 ) error("could not bind socket: " + socket_error(err) + "\n"); if ( (err = socket_listen(fdOwned, "listen_callback")) < 0 ) error("could not listen to socket: " + socket_error(err) + "\n"); SKTLOG("create: SKT_STYLE_LISTEN_M",fdOwned); break; case SKT_STYLE_CONNECT_M: #ifdef REQUIRE_PRIV if ( !check_previous_privilege(REQUIRE_PRIV) ) { error("Insufficient privs to open an outgoing socket.\n"); } #endif read_func = p2; close_func = p3; fdOwned = socket_create(0 /* MUD */, (: read_callback :), (: close_callback :)); if ( fdOwned < 0 ) error("could not create socket: " + socket_error(fdOwned) + "\n"); err = socket_connect(fdOwned, p1, "read_callback", "write_callback"); if ( err < 0 ) error("could not listen to socket: " + socket_error(err) + "\n"); SKTLOG("create: SKT_STYLE_CONNECT_M",fdOwned); SKTLOG("create: close_func",close_func); break; case SKT_STYLE_INT_ACQUIRE: read_func = p1; close_func = p2; break; } }