/* * Intermud services daemon * Original author: Huthar@Portals * Rewritten: Blackthorn@Genocide (10/29/92) * Cleaned up and unflakified by Truilkan@Basis (11/21/92) * Removed a redundant map_delete() call - Truilkan (11/21/92) * 93-07-30 Grendel@tmi-2 Removed defines, replaced with inetd.h */ #include <config.h> #include <net/daemons.h> #include <net/socket.h> #include <net/inetd.h> #include <net/macros.h> #define log(x) log_file("inetd", (x)) // function protypes void possible_close(int id); string read_socket(int id); int write_socket(int id, string msg); void load_services(); void create_listen_socket(); void close_socket(int id); void close_callback(int fd); void write_callback(int id); void read_callback(int fd, string msg); int create_socket(string dest); int open_service(string mud, string svc, string *parms); void process_incoming(int fd); // globals int listen_fd; mapping sockets, service; // indexed on numerical socket id // "incoming": incoming data waiting to be read // "outgoing": outgoing data waiting to be written // "owner": object that owns the socket // "wcb_pending": a write callback is pending on this socket // "closing": no further operations should be permitted on this socket // "read_callback": name of the owner's read callback function // "close_callback": name of the owner's close callback function // "service_callback": name of the owner's "service callback" function // "fd": socket's file descriptor // "service_desired": service that we want from the remote mapping service; // indexed on service name, contains string filename of server object void possible_close(int id) { if (!sockets[id]) { return; } if (sockets[id]["closing"]) { if (!strlen(sockets[id]["outgoing"]) && !strlen(sockets[id]["incoming"])) { socket_close(sockets[id]["fd"]); } } } string read_socket(int id) { int error; if (!sockets[id] || ((previous_object() != this_object()) && (previous_object() != sockets[id]["owner"]))) { log("read_socket: security violation on socket id " + id + "\n"); return 0; } if (strlen(sockets[id]["incoming"]) > 0) { string rcv; rcv = sockets[id]["incoming"]; sockets[id]["incoming"] = ""; possible_close(id); return rcv; } else return 0; } int write_socket(int id, string msg) { int error; if (!sockets[id] || ((previous_object() != this_object()) && (previous_object() != sockets[id]["owner"]))) { log("write_socket: security violation on socket id " + id + "\n"); return 0; } // it used to check for "closing" status here, but we should first be // allowed to empty out the "incoming" and "outgoing" buffers. // Patch by Zazz@WizMud, 3/26/93. if (sockets[id]["wcb_pending"]) { sockets[id]["outgoing"] += msg; } else { error = socket_write(sockets[id]["fd"], msg); if (error != EESUCCESS) { // on some machines this happens *alot* if (error == EEALREADY) { sockets[id]["outgoing"] += msg; // data *not* buffered sockets[id]["wcb_pending"] = 1; } if (error==EECALLBACK) { sockets[id]["wcb_pending"] = 1; } else return 0; } } // NOW check to see if the server has indicated it is through if (sockets[id]["closing"] == 1) { possible_close(id); // will close iff buffers empty return 0; } return 1; } void load_services() { string file, *lines, svc; string path; int i; service = ([]); file = read_file(INETD_SERVICES); if (!file) { log("load_services: cannot read inetd services file " + INETD_SERVICES + "\n"); return; } lines = explode(file, "\n"); for (i = 0; i < sizeof(lines); i++) { if (lines[i] == "" || lines[i][0] == '#') continue; if (sscanf(lines[i], "%s %s", svc, path) != 2) { log("load_services: error in services file, line " + (i + 1) + "\n"); continue; } service[svc] = path; } } void create_listen_socket() { int error; listen_fd = socket_create(STREAM, "read_callback", "close_callback"); if (listen_fd < 0) { log("create_listen_socket: socket_create: " + socket_error(listen_fd) + "\n"); return; } error = socket_bind(listen_fd, (int)DNS_MASTER->get_mudresource(mud_nname(), "inetd")); if (error != EESUCCESS) { socket_close(listen_fd); log("create_listen_socket: socket_bind: " + socket_error(listen_fd) + "\n"); return; } error = socket_listen(listen_fd, "read_callback"); if (error != EESUCCESS) { socket_close(listen_fd); log("create_listen_socket: socket_listen: " + socket_error(listen_fd) + "\n"); return; } } void close_socket(int id) { if (!sockets[id] || ((previous_object() != this_object()) && (previous_object() != sockets[id]["owner"]))) { log("close_socket: security violation on socket id " + id + "\n"); return 0; } sockets[id]["closing"] = 1; possible_close(id); } void close_callback(int fd) { if (fd == listen_fd) { log("close_callback: shutdown on listen fd " + fd + "\n"); return; } if(sockets[fd]["owner"]) call_other(sockets[fd]["owner"], sockets[fd]["close_callback"], fd); map_delete(sockets, fd); // nuke this puppy } void write_callback(int id) { sockets[id]["wcb_pending"] = 0; if (strlen(sockets[id]["outgoing"]) > 0) { string send; send = sockets[id]["outgoing"]; sockets[id]["outgoing"] = ""; this_object()->write_socket(id, send); possible_close(id); } } void read_callback(int fd, string msg) { string tmp1, tmp2; if (fd == listen_fd) { int new_fd; new_fd = socket_accept(fd, "read_callback", "write_callback"); if (new_fd < 0) { log("read_callback: socket_accept: " + socket_error(new_fd) + "\n"); return; } sockets[new_fd] = allocate_mapping(8); sockets[new_fd]["fd"] = new_fd; sockets[new_fd]["incoming"] = ""; sockets[new_fd]["outgoing"] = ""; sockets[new_fd]["read_callback"] = "read_callback"; sockets[new_fd]["close_callback"] = "close_callback"; sockets[new_fd]["service_callback"] = "service_callback"; sockets[new_fd]["service_status"] = AWAITING_SERVICE; write_socket(new_fd, "SERVICE?\n"); return; } if (!sockets[fd]) { log("read_callback: callback on unknown socket fd " + fd + "\n"); return; } sockets[fd]["incoming"] += msg; while (sscanf(sockets[fd]["incoming"], "%s\n%s", tmp1, tmp2) == 2) { sockets[fd]["incoming"] = tmp1 + "\n"; process_incoming(fd); sockets[fd]["incoming"] = tmp2; } } int create_socket(string dest) { int fd, error; fd = socket_create(STREAM, "read_callback", "close_callback"); if (fd < 0) { log("create_socket: socket_create: " + socket_error(fd) + "\n"); return -1; } error = socket_connect(fd, dest, "read_callback", "write_callback"); if (error != EESUCCESS) { socket_close(fd); log("create_socket: socket_connect: " + socket_error(error) + "\n"); return -1; } sockets[fd] = allocate_mapping(8); sockets[fd]["fd"] = fd; sockets[fd]["incoming"] = ""; sockets[fd]["owner"] = previous_object(); sockets[fd]["outgoing"] = ""; sockets[fd]["read_callback"] = "read_callback"; sockets[fd]["close_callback"] = "close_callback"; sockets[fd]["service_callback"] = "service_callback"; return fd; } int open_service(string mud, string svc, string *parms) { int id; string address; address = INETD_PORT(mud); if (!address) { return -1; } id = create_socket(address); if (id < 0) { return id; } sockets[id]["service_status"] = AWAITING_CONNECT_ACK; sockets[id]["service_desired"] = svc + (parms ? (" " + implode(parms, " ")) : ""); return id; } void process_incoming(int fd) { object ob; int error, l; string msg, svc, *parms; msg = this_object()->read_socket(fd); switch (sockets[fd]["service_status"]) { case AWAITING_CONNECT_ACK: if (msg == "SERVICE?\n") { this_object()->write_socket(fd, sockets[fd]["service_desired"] + "\n"); sockets[fd]["service_status"] = AWAITING_DATA; call_other(sockets[fd]["owner"], sockets[fd]["service_callback"], fd); return; } break; case AWAITING_DATA: call_other(sockets[fd]["owner"], sockets[fd]["read_callback"], fd, msg); break; case AWAITING_SERVICE: svc = msg[0..<2]; parms = explode(svc, " "); if (!service[parms[0]]) { write_socket(fd, "SERVICE NOT AVAILABLE\n"); close_socket(fd); } svc = parms[0]; service[svc]->dummy(); sockets[fd]["owner"] = find_object(service[svc]); sockets[fd]["service_status"] = AWAITING_DATA; service[svc]->service_request(fd, parms[1..<1]); break; default : break; } } void create() { seteuid(getuid(this_object())); sockets = ([]); load_services(); create_listen_socket(); } /* client: To initiate, use INETD->open_service(string mudname, string servicename, string *parms) The id of the socket assigned to your service request will be returned, or -1 for failure. the calling object is used as the owner. The function service_callback(id) will be called in the owner when the service is open and ready for data transfer. server: service_request(int id, string *parms) will be called in the server object of the appropriate service whenever a request for that service is made. The server object is set as owner. both: INETD->write_socket(int id, string msg) sends text out the socket the function read_callback(int id, string msg) will be called in the owner when data is received. The function close_callback(id) will be called in the owner if the socket closes from the other end, INETD->close_socket(int id) can be used to close it from our end */ mapping query_sockets() { if(adminp(geteuid(previous_object()))) return sockets; return 0; } mapping query_service() { if(adminp(geteuid(previous_object()))) return service; return 0; }