#include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(MAXHOSTNAMELEN) # define MAXHOSTNAMELEN 1024 #endif #include #define REGISTRATION_SERVER "mail.coli.uni-sb.de" #ifdef DEBUG # define REGISTRATION_DELAY 60 #else # define REGISTRATION_DELAY 600 #endif #define OE_STATUS_BUSY 1 #define OE_STATUS_GC 2 #define OE_STATUS_REGISTRATION 4 static void _register(int); static int register_handler(ClientData, Tcl_Interp *, int); static void _resume(int); static int resume_handler(ClientData, Tcl_Interp *, int); static void _gc_start(int); static void _gc_end(int); static int gc_handler(ClientData, Tcl_Interp *, int); static void save_excursion(Tcl_Interp *, int); static void exit_handler(ClientData); int oe_init(Tcl_Interp *); static int oe(ClientData, Tcl_Interp *, int, char **); static int oe_register(char *); static int validate_key(char *); static int socket_write(int, char *); static int socket_readline(int, char *, int); /* * all of this is unpleasantly complicated: because we want the registrar * process to be created at wish(1) startup, we cannot yet access the program * and version information as it is only set from the (Lisp) application. * * hence, we have to preserve a communication channel between wish(1) and the * registrar process and install a signal handler in wish(1) that will read out * the requested information and communicate through the pipe |:-(. */ static unsigned char oe_status = 0; static int _backdoor[2] = {-1, -1}; static Tcl_AsyncHandler _register_handler; static Tcl_AsyncHandler _resume_handler; static Tcl_AsyncHandler _gc_handler; static Tcl_Interp *_global; static void _register(int self) { #ifdef DEBUG fprintf(stderr, "_register(): entry.\n"); #endif Tcl_AsyncMark(_register_handler); signal(self, _register); } /* _register() */ static int register_handler(ClientData client_data, Tcl_Interp *interpreter, int code) { static char *register_message = (char *)NULL; char *key, *foo; Tcl_Interp *global = (Tcl_Interp *)client_data; #ifdef DEBUG fprintf(stderr, "register_handler(): entry.\n"); #endif if(register_message == NULL) { register_message = strdup("copyleft register"); } /* if */ key = (char *)NULL; if(_backdoor[0] >= 0 && global != NULL && (foo = Tcl_GetVar2(global, "globals(copyleft,key)", (char *)NULL, TCL_GLOBAL_ONLY|TCL_PARSE_PART1)) != NULL) { key = strdup(foo); socket_write(_backdoor[0], foo); } /* if */ socket_write(_backdoor[0], "@"); if(_backdoor[0] >= 0 && global != NULL && (foo = Tcl_GetVar2(global, "globals(name)", (char *)NULL, TCL_GLOBAL_ONLY|TCL_PARSE_PART1)) != NULL) { socket_write(_backdoor[0], foo); } /* if */ else { socket_write(_backdoor[0], "swish++"); } /* else */ socket_write(_backdoor[0], " "); if(_backdoor[0] >= 0 && global != NULL && (foo = Tcl_GetVar2(global, "globals(version)", (char *)NULL, TCL_GLOBAL_ONLY|TCL_PARSE_PART1)) != NULL) { socket_write(_backdoor[0], foo); } /* if */ else { socket_write(_backdoor[0], "(unknown)"); } /* else */ close(_backdoor[0]); _backdoor[0] = -1; if(validate_key(key)) { return(code); } /* if */ save_excursion(interpreter, 0); Tcl_GlobalEval(global, register_message); save_excursion(interpreter, 1); #ifdef DEBUG fprintf(stderr, "register_handler(): exit.\n"); #endif return(code); } /* register_handler() */ static void _resume(int self) { #ifdef DEBUG fprintf(stderr, "_resume(): entry.\n"); #endif oe_status |= OE_STATUS_REGISTRATION; Tcl_SetVar2(_global, "globals(copyleft,status)", (char *)NULL, "done", TCL_GLOBAL_ONLY|TCL_PARSE_PART1); Tcl_AsyncMark(_resume_handler); } /* _resume() */ static int resume_handler(ClientData client_data, Tcl_Interp *interpreter, int code) { #ifdef DEBUG fprintf(stderr, "resume_handler(): entry.\n"); #endif Tcl_ReapDetachedProcs(); #ifdef DEBUG fprintf(stderr, "resume_handler(): exit.\n"); #endif return(code); } /* resume_handler() */ static void _gc_start(int self) { oe_status |= OE_STATUS_GC; Tcl_AsyncMark(_gc_handler); signal(self, _gc_start); } /* _gc_start() */ static void _gc_end(int self) { oe_status &= ~OE_STATUS_GC; Tcl_AsyncMark(_gc_handler); signal(self, _gc_end); } /* _gc_end() */ static int gc_handler(ClientData client_data, Tcl_Interp *interpreter, int code) { static char *gc_message = (char *)NULL; Tcl_Interp *global = (Tcl_Interp *)client_data; #ifdef DEBUG if(code == TCL_ERROR) { fprintf(stderr, "gc_handler(): mysterious errorneous context :-{.\n"); } /* if */ #endif if(gc_message == NULL) { gc_message = strdup("tsdb_busy"); } /* if */ /* * _fix_me_ * as it stands, the Tcl_Eval() callback causes problems: sometimes the * result of a previously executed command is lost; and, somehow, saving * and restoring `interpreter->result' never seems to save anything. * (16-jun-99 - oe) */ save_excursion(interpreter, 0); Tcl_GlobalEval(global, gc_message); save_excursion(interpreter, 1); return(code); } /* register_handler() */ static void exit_handler(ClientData client_data) { long int child = client_data; if(child && child != getpid() && kill(child, SIGHUP)) { if(kill(child, SIGTERM)) { sleep(1); kill(child, SIGKILL); } /* if */ } /* if */ } /* exit_handler() */ static void save_excursion(Tcl_Interp *interpreter, int restore) { static char *buffer; static int size = 0; int n; /* * _fix_me_ * we are using the deprecated direct access to interpreter->result for the * time being; should probably switch to Tcl_SetResult() and friends some * time. (16-jun-99 - oe) */ if(!size) { size = 1024; buffer = (char *)malloc(size + 1); } /* if */ if(interpreter != NULL) { if(!restore) { if((n = strlen(interpreter->result)) > size) { size = n; buffer = (char *)realloc(buffer, size + 1); } /* if */ strcpy(buffer, interpreter->result); #ifdef DEBUG fprintf(stderr, "save_excursion(): <-- `%s'.\n", buffer); #endif } /* if */ else { Tcl_ResetResult(interpreter); #ifdef DEBUG fprintf(stderr, "save_excursion(): --> `%s'.\n", buffer); #endif Tcl_SetResult(interpreter, buffer, TCL_VOLATILE); } /* else */ } /* if */ } /* save_excursion() */ int oe_init(Tcl_Interp *interpreter) { /* * install asynchronous event handlers in wish(1) process that get called * by registrar process (i) when starting the registration process and (ii) * upon successful completion. */ long int child; if(!pipe(_backdoor)) { setsid(); _global = interpreter; _register_handler = Tcl_AsyncCreate(register_handler, (ClientData)interpreter); _resume_handler = Tcl_AsyncCreate(resume_handler, (ClientData)interpreter); signal(SIGUSR1, _register); signal(SIGUSR2, _resume); _gc_handler = Tcl_AsyncCreate(gc_handler, (ClientData)interpreter); signal(SIGTTIN, _gc_start); signal(SIGTTOU, _gc_end); if(0 && (child = oe_register(REGISTRATION_SERVER)) > 0) { Tcl_CreateExitHandler(exit_handler, (ClientData)child); } /* if */ } /* if */ Tcl_CreateCommand(interpreter, "oe", oe, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL); return(TCL_OK); } /* oe_init */ static int oe(ClientData client_data, Tcl_Interp *interpreter, int argc, char **argv) { char *user; if (argc == 2 && !strcmp(argv[1], "user")) { if ((user = getlogin()) != NULL) { Tcl_SetResult(interpreter, strdup(user), TCL_VOLATILE); } /* if */ return(TCL_OK); } /* if */ if (argc == 2 && !strcmp(argv[1], "busy")) { sprintf(interpreter->result, "%s", (oe_status & OE_STATUS_BUSY ? "yes" : "no")); return(TCL_OK); } /* if */ if (argc == 2 && !strcmp(argv[1], "registration")) { sprintf(interpreter->result, "%s", (oe_status & OE_STATUS_REGISTRATION ? "yes" : "no")); return(TCL_OK); } /* if */ if (argc == 3 && !strcmp(argv[1], "busy")) { if(!strcmp(argv[2], "on") || !strcmp(argv[2], "yes") || !strcmp(argv[2], "1")) { oe_status |= OE_STATUS_BUSY; } /* if */ else { oe_status &= ~OE_STATUS_BUSY; } /* else */ sprintf(interpreter->result, "%s", (oe_status & OE_STATUS_BUSY ? "yes" : "no")); return(TCL_OK); } /* if */ if (argc == 2 && !strcmp(argv[1], "gc")) { sprintf(interpreter->result, "%s", (oe_status & OE_STATUS_GC ? "yes" : "no")); return(TCL_OK); } /* if */ if (argc == 3 && !strcmp(argv[1], "gc")) { if(!strcmp(argv[2], "on") || !strcmp(argv[2], "yes") || !strcmp(argv[2], "1")) { oe_status |= OE_STATUS_GC; } /* if */ else { oe_status &= ~OE_STATUS_GC; } /* else */ sprintf(interpreter->result, "%s", (oe_status & OE_STATUS_GC ? "yes" : "no")); return(TCL_OK); } /* if */ if (argc == 2 && !strcmp(argv[1], "reap")) { Tcl_ReapDetachedProcs(); return(TCL_OK); } /* if */ if (argc == 3 && !strcmp(argv[1], "copyleft")) { sprintf(interpreter->result, "%d", validate_key(argv[2])); return(TCL_OK); } /* if */ else { Tcl_SetResult(interpreter, "invalid call to oe()", TCL_STATIC); return(TCL_ERROR); } /* else */ } /* oe */ static int oe_register(char *server) { char *user, *fqdn, *key, *program, *foo; char host[MAXHOSTNAMELEN]; pid_t ppid, pid; int client, i, n, flags; Tcl_Pid *pids; struct hostent *local, *remote; struct sockaddr_in server_address; struct linger linger; fd_set fds; struct timeval timeout; char command[4096], input[4096]; if ((ppid = getpid()) < 0) { return(-1); } /* if */ if((pid = fork()) == -1) { return(-2); } /*if */ else if(pid) { #ifdef DEBUG fprintf(stderr, "oe_register(): fork(2)ed child %d\n", pid); #endif close(_backdoor[1]); pids = (Tcl_Pid *)malloc(sizeof(Tcl_Pid)); pids[0] = (Tcl_Pid)pid; Tcl_DetachPids(1, pids); return(pid); } /* if */ close(_backdoor[0]); #ifndef DEBUG for(i = 0; i < 256; i++) { if(i != _backdoor[1]) { close(i); } /* if */ } /* for */ #endif /* * here is the plan: the background process hangs around for a while to * allow the podium to settle down (e.g. receive status information from the * Lisp application) and to give users who prefer privacy a chance to prevent * registration by quitting the podium). then we have to decide whether the * current user and host are registered customers already, or whether we want * to generate a log message and send it to Saarbruecken. * though this should not happen (because of signal propagation among the * process group), the registrar process checks the existence of the parent * process before generating a message; thus, termination of the podium * effectively blocks registration. */ sleep(REGISTRATION_DELAY); if ((user = getlogin()) != NULL) { user = strdup(user); } /* if */ else { user = strdup("anonymous"); } /* else */ fqdn = (char *)NULL; if(!gethostname(&host[0], MAXHOSTNAMELEN)) { if(strchr(&host[0], '.') != NULL) { fqdn = strdup(&host[0]); } /* if */ else { if((local = gethostbyname(&host[0])) != NULL) { if(strchr(local->h_name, '.') != NULL) { fqdn = strdup(local->h_name); (void)strcpy(&host[0], fqdn); } /* if */ else { if((local = gethostbyaddr((char *)local->h_addr_list[0], local->h_length, AF_INET)) != NULL) { if(strchr(local->h_name, '.') != NULL) { fqdn = strdup(local->h_name); (void)strcpy(&host[0], fqdn); } /* if */ } /* if */ } /* else */ } /* if */ } /* else */ } /* if */ else { (void)strcpy(&host[0], "unknown"); } /* else */ if(kill(ppid, SIGUSR1)) { exit(1); } /* if */ if(socket_readline(_backdoor[1], &input[0], 4096) <= 0) { exit(1); } /* if */ if((foo = strchr(&input[0], '@')) == NULL) { exit(1); } /* if */ *foo = (char)0; key = strdup(&input[0]); program = strdup(++foo); close(_backdoor[1]); if(validate_key(key)) { exit(0); } /* if */ for(i = 0; i < 42; i++) { if((remote = gethostbyname(server)) == NULL) { sleep(REGISTRATION_DELAY); continue; } /* if */ bzero((char *)&server_address, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = *(unsigned long int *)remote->h_addr; server_address.sin_port = htons(25); if((client = socket(AF_INET, SOCK_STREAM, 0)) < 0) { sleep(REGISTRATION_DELAY); continue; } /* if */ setsockopt(client, SOL_SOCKET, SO_KEEPALIVE, (char *)&n, sizeof(n)); setsockopt(client, SOL_SOCKET, SO_REUSEADDR, (char *)&n, sizeof(n)); linger.l_onoff = 1; linger.l_linger = 10; setsockopt(client, SOL_SOCKET, SO_LINGER, (char *)&linger, sizeof(linger)); /* * remember, we have already signaled the wish process once. */ if(i && kill(ppid, SIGUSR1)) { exit(1); } /* if */ /* * this all is somewhat complicated (just like life in general): because * (apparently) there is no way to set the timeout that connect(3) waits * before failure, we set the socket into non-blocking mode (thus, the * call to connect(3) returns immediately) and then use select(3) to wait * for a while until input becomes available. if not, we stall for a * little while and then reiterate the entire process. */ if((flags = fcntl (client, F_GETFL, 0)) != -1) { flags |= O_NONBLOCK; fcntl(client, F_SETFL, flags); } /* if */ if(connect(client, (struct sockaddr *)&server_address, sizeof(server_address)) == -1 && errno != EINPROGRESS) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ FD_ZERO(&fds); FD_SET(client, &fds); timeout.tv_sec = 60; timeout.tv_usec = 0; if(select(FD_SETSIZE, &fds, (fd_set *)NULL, (fd_set *)NULL, &timeout) != 1) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ /* * from now on we assume there is an open connection; hence, revert the * socket to regular (blocking) mode (non-blocking is inherited from the * value used when opening the descriptor). */ if((flags = fcntl (client, F_GETFL, 0)) != -1) { flags &= ~O_NONBLOCK; fcntl(client, F_SETFL, flags); } /* if */ if(socket_readline(client, &input[0], 4096) < 3 || strncmp(&input[0], "220", 3)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ /* * it seems we are talking to an SMTP server; since the actual transaction * of the registration message may take a while, pretend that registration * was completed already. if it turns out, we fail to complete our chat * with the server, the registrar process will pop up a notification again * the next time we run this loop. */ kill(ppid, SIGUSR2); (void)sprintf(command, "HELO %s\r\n", host); if(socket_write(client, command) != strlen(command)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ if(socket_readline(client, &input[0], 4096) < 3 || strncmp(&input[0], "250", 3)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ if(fqdn != NULL) { (void)sprintf(command, "MAIL FROM: <%s@%s>\r\n", user, fqdn); } /* if */ else { (void)sprintf(command, "MAIL FROM: <>\r\n"); } /* else */ if(socket_write(client, command) != strlen(command)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ if(socket_readline(client, &input[0], 4096) < 3 || strncmp(&input[0], "250", 3)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ (void)sprintf(command, "RCPT TO: \r\n"); if(socket_write(client, command) != strlen(command)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ if(socket_readline(client, &input[0], 4096) < 3 || strncmp(&input[0], "250", 3)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ (void)sprintf(command, "DATA\r\n"); if(socket_write(client, command) != strlen(command)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ if(socket_readline(client, &input[0], 4096) < 3 || strncmp(&input[0], "354", 3)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ (void)sprintf(command, "To: podium@coli.uni-sb.de\r\n" "From: %s@%s\r\n" "Subject: %s for `%s' on `%s'\r\n" ".\r\n", user, host, program, user, host); if(socket_write(client, command) != strlen(command)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ if(socket_readline(client, &input[0], 4096) < 3 || strncmp(&input[0], "250", 3)) { close(client); sleep(REGISTRATION_DELAY); continue; } /* if */ (void)sprintf(command, "QUIT\r\n"); (void)socket_write(client, command); sleep(2); close(client); exit(0); } /* for */ exit(1); } /* oe_register() */ static int validate_key(char *key) { return((key != NULL ? !strcmp(key, "42") : 0)); } /* validate_key() */ static int socket_write(int socket, char *string) { /*****************************************************************************\ |* file: |* module: socket_write() |* version: |* written by: oe, CoLi saarbruecken |* last update: |* updated by: |*****************************************************************************| |* \*****************************************************************************/ int written, left, n; #ifdef DEBUG fprintf(stderr, "socket_write(): `%s'\n", string); #endif for(written = 0, left = n = strlen(string); left > 0; left -= written, string += written) { if((written = write(socket, string, left)) == -1) { return(-1); } /* if */ } /* for */ return(n - left); } /* socket_write() */ static int socket_readline(int socket, char *string, int length) { /*****************************************************************************\ |* file: |* module: socket_readline() |* version: |* written by: oe, CoLi saarbruecken |* last update: |* updated by: |*****************************************************************************| |* socket_readline() reads from .socket. until (length - 1) characters have |* been read, a newline has been seen or EOF is encountered; .string. is |* always terminated by a 0 character (substituting for the closing newline if |* one was found). \*****************************************************************************/ int i, n; char c; for(i = n = 0; n < (length - 1) && (i = read(socket, &c, 1)) == 1; n++) { if(c == EOF) { return(-1); } /* if */ if(c == '\r') { (void)read(socket, &c, 1); } /* if */ if(c == '\n') { string[n] = 0; return(n + 1); } /* if */ string[n] = c; } /* for */ if(i == -1) { return(-1); } /* if */ else if(n && !i) { string[n] = 0; return(n + 1); } /* if */ else { return(0); } /* else */ } /* socket_readline() */