/* $Cambridge: hermes/src/prayer/accountd/accountd.c,v 1.3 2004/07/16 09:36:11 dpc22 Exp $ */ /************************************************ * Prayer - a Webmail Interface * ************************************************/ /* Copyright (c) University of Cambridge 2000 - 2002 */ /* See the file NOTICE for conditions of use and distribution. */ /* Prayer account management daemon */ #include "accountd.h" static void ucase(char *s) { while (*s) { *s = Utoupper(*s); s++; } } /* ====================================================================== */ /* Process line of input once authenicated */ static BOOL do_authenicated_line(struct config *config, char *line, struct iostream *stream, char *username) { char *cmd; if (!(cmd = string_get_token(&line))) { ioputs(stream, "BAD Invalid command" CRLF); ioflush(stream); return (T); } ucase(cmd); log_misc("%s: %s", username, cmd); switch (cmd[0]) { case 'C': if (!strcmp(cmd, "CHECKFULLNAME")) { fullname_check(config, stream); return (T); } break; case 'F': if (!strcmp(cmd, "FULLNAME")) return (fullname_change(config, stream, line)); break; case 'G': if (!strcmp(cmd, "GET")) return (file_get(config, stream, line)); break; case 'M': /* Merge into single mail_upload command */ if (!strcmp(cmd, "MAILSTATUS")) return (mail_status(config, stream, line)); else if (!strcmp(cmd, "MAILCHANGE")) return (mail_change(config, stream, line)); break; case 'L': if (!strcmp(cmd, "LOGOUT")) { ioputs(stream, "OK Logout" CRLF); ioflush(stream); return (NIL); } else if (!strcmp(cmd, "LOGIN")) { ioputs(stream, "NO Already authenicated" CRLF); ioflush(stream); return (T); } break; case 'P': if (!strcmp(cmd, "PASSWORD")) return (password_change(config, stream, line)); else if (!strcmp(cmd, "PUT")) return (file_put(config, stream, line)); break; case 'Q': if (!strcmp(cmd, "QUOTA")) { quota_check(config, stream); return (T); } break; case 'V': if (!strcmp(cmd, "VACATION_CLEAR")) return (mail_vacclear(config, stream, line)); break; } ioputs(stream, "BAD Unknown command" CRLF); ioflush(stream); return (T); } /* ====================================================================== */ /* Run a single accountd session on the nominated socket descriptor */ static BOOL session(struct config *config, int sockfd) { char buffer[MAXLENGTH]; char *username = NIL; struct iostream *stream; if (! (stream = iostream_create(NIL, sockfd, IOSTREAM_PREFERRED_BLOCK_SIZE))) return (NIL); iostream_set_timeout(stream, ACCOUNTD_TIMEOUT); if (config->use_ssl && !iostream_ssl_start_server(stream)) return (NIL); if (getuid() == 0) { if (!authenicate(config, stream, &username)) return (NIL); } else { if (!authenicate_preauth(config, stream, &username)) return (NIL); } if (getuid() == 0) { log_fatal("Authenicated session running with root privilege"); /* NOTREACHED */ exit(1); } log_misc("User login: %s", username); while (1) { if (!iostream_getline(stream, buffer, MAXLENGTH)) break; if (buffer[0] == '\0') /* Ignore empty lines */ continue; if (!do_authenicated_line(config, buffer, stream, username)) break; } log_misc("User logout: %s", username); return (T); } /* ====================================================================== */ /* Code for running accountd as standalone daemon rather than from inetd */ void rundaemon(struct config *config, int use_fork, int port) { struct ipaddr ipaddr; int sockfd, newsockfd; pid_t childpid; if ((sockfd = os_bind_inet_socket(port)) < 0) log_fatal("socket() failed"); os_signal_child_init(os_child_reaper); if (config->use_ssl) iostream_init(config); /* Required for SSL stuff */ for (;;) { if ((newsockfd = os_accept_inet(sockfd, &ipaddr)) < 0) log_fatal("accept() failed: errno = %d", errno); log_misc("Incoming connection from %s", ipaddr_text(&ipaddr)); if (use_fork) { if ((childpid = fork()) < 0) log_fatal("fork() failed: errno = %d", errno); if (childpid == 0) { /* Child process */ close(sockfd); os_signal_child_clear(); session(config, newsockfd); close(newsockfd); exit(0); } } else session(config, newsockfd); /* Parent */ close(newsockfd); #ifdef USE_SSL /* Replace (shared) RSA key every few minutes */ if (config->use_ssl) iostream_check_rsakey(config); #endif } } /* ====================================================================== */ /* Main routine: * Parse command line options, then run as either permanant daemon or * single shot login using file descriptor 0 for input and output */ int main(int argc, char **argv) { BOOL use_fork = T; BOOL hermes = NIL; char *config_filename = ACCOUNTD_CONFIG_FILE; struct config *config = config_create(); int i; /* Disable supplementary groups, switch to group "other" (if it exists) */ if (getuid() == 0) { struct group *group; setgroups(0, NIL); if ((group = getgrnam("hermes")) != NIL) setgid(group->gr_gid); if ((group = getgrnam("other")) != NIL) setgid(group->gr_gid); else if ((group = getgrnam("user")) != NIL) setgid(group->gr_gid); } if (getenv("ACCOUNTD_CONFIG_FILE")) config_filename = getenv("ACCOUNTD_CONFIG_FILE"); log_init(config, argv[0]); /* Minimal Umask: don't want anyone reading user .forward files. */ umask(0077); for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--enable-fork")) use_fork = T; else if (!strcmp(argv[i], "--disable-fork")) use_fork = NIL; else if (!strcmp(argv[i], "--hermes")) { hermes = T; } else if (!strncmp(argv[i], "--config-file=", strlen("--config-file="))) config_filename = strdup(argv[i] + strlen("--config-file=")); else if (!strcmp(argv[i], "--config-option")) i++; /* Processes next argv */ else if (!strcmp(argv[i], "--help")) { fprintf(stderr, "Command Line Options:\n"); fprintf(stderr, (" --config-file=x " "Define location of configuration file\n")); fprintf(stderr, (" --config-option x=y " "Override option from configuration file\n")); fprintf(stderr, (" --enable-fork " "Allow fork if running as daemon (default)\n")); fprintf(stderr, (" --disable-fork " "Disable fork if running as daemon (debugging)\n")); exit(0); } else log_fatal("Unknown command line option"); } if (!config_parse_file(config, config_filename)) exit(1); for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--config-option")) { if (++i < argc) { if (!config_parse_option(config, strdup(argv[i]))) exit(1); } else fprintf(stderr, "--config processes following option"); } } if (!config_check(config)) exit(1); if (hermes) config->filter_restricted = T; /* Required for SSL stuff */ #ifdef USE_SSL if (config->use_ssl) iostream_init(config); #endif /* Run as daemon on nominated port */ if (config->accountd_port) { rundaemon(config, use_fork, config->accountd_port); /* NOTREACHED */ exit(0); } /* Otherwise run as single shot session on file descriptor 0 * i.e: inetd service */ session(config, 0); exit(0); }