/***************************************************************************** POPular -- A POP3 server and proxy for large mail systems $Id: pcontrol.c,v 1.40 2003/11/22 21:27:22 sqrt Exp $ http://www.remote.org/jochen/mail/popular/ ****************************************************************************** Copyright (C) 1999-2003 Jochen Topf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA *****************************************************************************/ #if HAVE_CONFIG_H # include #endif #include "popular.h" /* prompt for interactive use */ #define PROMPT_TEMPLATE "%s %s > " /* timeout (in seconds) when waiting for answer from server */ #define TIMEOUT 10 /* program name for error messages */ static char *prgname; /* file descriptor of control socket */ static int pcontrol_fd; /* verbosity level 0=quiet, 1=normal, 2=verbose */ static int verbosity; /* ignore errors when executing commands */ static int ignore; /* UNIX domain socket name of server */ static char server_sockname[MAXBUF]; /* UNIX domain socket name of client */ static char client_sockname[MAXBUF]; /* server type as returned by id command */ static char server_type[MAXBUF]; int debug; /***************************************************************************** help texts *****************************************************************************/ struct help_text { char *id; char *name; char *text; } help_text[] = { { "pproxy", "help", "pcontrol commands:\n\ /h this message\n\ /h rc help on return codes\n\ /h help on command\n\ /q quit pcontrol\n\ \n\ pproxy commands:\n\ debug show/set debug modes\n\ show show settings\n\ set change settings\n\ pdm load/unload/configure POPular database modules (PDM)\n\ capa manipulate capability lists\n\ vserv virtual server configuration\n\ backend backend configuration\n\ shutdown shutdown server\n\ prng seed prng (SSL/TLS only)\n\ "}, { "pserv", "help", "pcontrol commands:\n\ /h this message\n\ /h rc help on return codes\n\ /h help on command\n\ /q quit pcontrol\n\ \n\ pserv commands:\n\ debug show/set debug modes\n\ show show settings\n\ set change settings\n\ capa manipulate capability list\n\ server put server online and offline\n\ shutdown shutdown server\n\ "}, { "*", "rc", "return codes:\n\ 00 ok\n\ \n\ 10 unknown object\n\ 11 object not supported (because TLS support was not compiled in etc.)\n\ 12 object can't be changed (read-only)\n\ 13 invalid value for this object\n\ 15 unknown object or max number for this object already reached\n\ 16 missing data\n\ 17 command not allowed in this state\n\ 18 object in use\n\ \n\ 20 wrong argument count\n\ 21 unknown command\n\ 22 unknown subcommand\n\ 23 unknown keyword\n\ 24 not allowed with TLS protocol\n\ 25 not allowed with STARTTLS setting\n\ 26 setting not allowed because TLS not compiled in\n\ 29 parse error\n\ \n\ 31 bind error (maybe the port is already bound?)\n\ 32 out of memory error\n\ 33 file i/o error\n\ 34 operation failed\n\ 35 TLS error, see log file\n\ \n\ 40 module load error\n\ \n\ 99 internal error (If you see this, report it as a bug!)\n\ "}, { "pproxy", "debug", "Usage: debug [MODE [OPTION] ...]\n\ Set/clear/show debug options.\n\ \n\ modes:\n\ + set the following debug options\n\ - clear the following debug options\n\ = set exactly the following debug options and clear all others\n\ \n\ options:\n\ MAIN main program, general messages about what the program is doing\n\ AUTH authentication details (but without passwords)\n\ PASS passwords are logged (this might be a security risk!)\n\ POP POP3 dialogs are logged\n\ CTRL control requests and answers\n\ NET network errors etc.\n\ TLS TLS messages (pproxy only, only if compiled in)\n\ \n\ pseudo-options:\n\ ALL All options together\n\ NONE No options at all\n\ \n\ examples:\n\ debug Show current debug options\n\ debug + AUTH PASS Add AUTH and PASS options to already existing options\n\ debug = NET Set only NET debug option\n\ debug = ALL Set all debug options\n\ debug = NONE Delete all debug options\n\ debug = Delete all debug options\n\ "}, { "pproxy", "show", "Usage: show [VARIABLE]\n\ Show settings of variable VARIABLE. If not argument is given, list all\n\ variable names.\n\ \n\ read-only variables:\n\ id id (name) of server (always 'pproxy')\n\ pid process id of server\n\ sessionlimit maximum compiled in value for maxsession\n\ rundir directory for pidfile, control sockets etc.\n\ version version of server\n\ \n\ config variables:\n\ allowsslv2 Allow SSL v2 connections (default: off)\n\ authtimeout timeout for authorization state\n\ backlog listen(2) backlog value\n\ capadir directory with capability lists\n\ checkport UDP port where pcheckd listens\n\ checktimeout timeout while waiting for answer from pcheckd\n\ defaultns default namespace\n\ fallback fallback backend if user can't be authenticated locally\n\ idletimeout timeout on idle sessions\n\ logfile name of logfile\n\ maxlocalload if the system load is equal or higher than this, no new\n\ connections are accepted (0=check disabled)\n\ maxsession maximum number of proxy sessions\n\ pdmdir directory with databases modules (pdm_*.so)\n\ proxytimeout timeout while proxying data\n\ sessiontimeout timeout for whole session\n\ tlsdir directory with TLS key files\n\ "}, { "pserv", "show", "Usage: show [VARIABLE]\n\ Show settings of variable VARIABLE. If not argument is given, list all\n\ variable names.\n\ \n\ read-only variables:\n\ id id (name) of server (always 'pserv')\n\ pid process id of server\n\ rundir directory for pidfile, control sockets etc.\n\ sessionlimit maximum compiled in value for maxsession\n\ version version of server\n\ \n\ config variables:\n\ backlog listen(2) backlog value\n\ capadir directory with capability lists\n\ idletimeout timeout on idle sessions\n\ localip IP used to bind to listening port (or 'ANY' for INADDR_ANY)\n\ logeachmsg after each connection log status of every mail\n\ logfile name of logfile\n\ maxlocalload if the system load is equal or higher than this, no new\n\ connections are accepted (0=check disabled)\n\ maxsession maximum number of server sessions\n\ popdir directory with POP3 mailboxes\n\ servport TCP port where the server should listen\n\ sessiontimeout timeout for whole session\n\ statusheader whether to print a 'Status: RO' header line in read mail\n\ "}, { "pproxy", "set", "Usage: set VARIABLE CONTENT\n\ \n\ Set content of variable VARIABLE to CONTENT. See help for 'show' command for\n\ a list of variables.\n\ "}, { "pproxy", "prng", "Usage: prng FILENAME [BYTES]\n\ \n\ Seed the pseudo random number generator (PRNG) from a file. The filename\n\ is normally /dev/random or /dev/urandom. BYTES defaults to 1024.\n\ "}, { "pproxy", "pdm", "Usage: PDM SUBCOMMAND [ARG] ...\n\ \n\ subcommands:\n\ pdm list list all loaded modules\n\ pdm flush unload all modules\n\ pdm add ID MOD ARG ... add module to end of list\n\ pdm del ID unload module from list\n\ pdm reload ID reload module\n\ pdm show ID show module parameters\n\ \n\ "}, { "pproxy", "capa", "Usage: capa SUBCOMMAND [NAME]\n\ \n\ subcommands:\n\ capa list list all capability list names\n\ capa load NAME load the capability list named NAME\n\ capa del NAME delete the named capability list\n\ capa show NAME show capabilities if the named list\n\ \n\ If a named capability list is loaded and there already exists a capability\n\ list with that name, the new one will replace the old list. If something\n\ goes wrong while reading in a capability list, any existing capability list\n\ will be unchanged.\n\ "}, { "pserv", "capa", "Usage: capa SUBCOMMAND [NAME]\n\ \n\ subcommands:\n\ capa show show current capability list setting\n\ capa set NAME set capability list\n\ \n\ NAME can be one of: 'ERROR', 'NONE', 'DEFAULT' or any name of a file in\n\ capadir.\n\ "}, { "pproxy", "backend", "Usage: backend SUBCOMMAND [ARG] ...\n\ \n\ subcommands:\n\ backend list list all configured backends\n\ backend show BACKEND show config of BACKEND\n\ backend del BACKEND delete BACKEND\n\ backend conf BACKEND (ARG VALUE)... configure backend\n\ backend flush flush (delete) all backends\n\ \n\ arguments for 'conf' subcommand are:\n\ \n\ host host name or IP number of backend\n\ port TCP port where server listens on backend\n\ prot protocol used to talk to this backend (POP3, XPOP, CXPOP)\n\ state state of this backend (OFFLINE, FAKE, ONLINE)\n\ "}, { "pproxy", "vserv", "Usage: vserv SUBCOMMAND [ARG] ...\n\ \n\ subcommands:\n\ vserv list list all configured virtual servers\n\ vserv show VSERV show config of VSERV\n\ vserv del VSERV delete VSERV\n\ vserv conf VSERV (ARG VALUE)... configure VSERV\n\ vserv flush flush (delete) all virtual servers\n\ \n\ arguments for 'conf' subcommand are:\n\ \n\ iface host name or IP number of local interface for virtual server\n\ port TCP port where virtual server listens on\n\ prot protocol used to talk to client (POP3, POP3s)\n\ starttls set usage of STARTTLS (OFF, OPTIONAL, FORCE)\n\ capa capability list to use (ERROR, NONE, DEFAULT, )\n\ state state of this virtual server (OFFLINE, DISABLED, FAKE, ONLINE)\n\ namespace namespace for connections on this virtual server\n\ bannerok greeting banner if virtual server is online\n\ bannererr greeting banner if virtual server if diabled\n\ "}, { "pproxy", "shutdown", "Usage: shutdown server|children|all|delayed\n\ shutdown server Shut down parent. Children are left running\n\ shutdown children Kill all children. Parent keeps running.\n\ shutdown all Kill all children and exit parent.\n\ shutdown delayed Stop accepting new connections and exit parent after\n\ all sessions have ended.\n\ "}, { "pserv", "shutdown", "Usage: shutdown server|children|all|delayed\n\ shutdown server Shut down parent. Children are left running\n\ shutdown children Kill all children. Parent keeps running.\n\ shutdown all Kill all children and exit parent.\n\ shutdown delayed Stop accepting new connections and exit parent after\n\ all sessions have ended.\n\ "}, { "pserv", "server", "Usage: server status|online|offline\n\ Set server status\n\ \n\ server status show server status\n\ server online take server online\n\ server offline take server offline\n\ "}, { NULL, NULL, NULL } }; /***************************************************************************** print_help() Prints help messages. *****************************************************************************/ void print_help(const char *id, const char *cmd) { int i; for (i=0; help_text[i].name; i++) { if (strcasecmp(help_text[i].name, cmd)) continue; if (strcasecmp(help_text[i].id, id) && help_text[i].id[0] != '*') continue; puts(help_text[i].text); return; } printf("No help for '%s'\n", cmd); } /***************************************************************************** signal_handler() *****************************************************************************/ void signal_handler(int signum) { (void) unlink(client_sockname); exit(0); } /***************************************************************************** cleanup() Is called after exit() to clean up UNIX domain socket file. *****************************************************************************/ void cleanup(void) { (void) unlink(client_sockname); } /***************************************************************************** send_command() Send one command to the server and wait for a reply. Returns a code corresponding to the first char of the answer. *****************************************************************************/ int send_command(const char *cmd) { int len; struct sockaddr_un server_addr; char answer[MAXBUF]; if (verbosity >= 2) printf("Sending command: %s\n", cmd); /*************************************************************************** Send command to server. ***************************************************************************/ memset(&server_addr, 0, sizeof(server_addr)); server_addr.sun_family = AF_UNIX; (void) strlcpy(server_addr.sun_path, server_sockname, sizeof(server_addr.sun_path)); len = sendto(pcontrol_fd, cmd, strlen(cmd), 0, (struct sockaddr *) &server_addr, sizeof(server_addr)); if (len != strlen(cmd)) { fprintf(stderr, "%s: sendto error: %s\n", prgname, strerror(errno)); exit(RCODE_CERR); } /*************************************************************************** Wait until server is ready. ***************************************************************************/ { fd_set readset; int r; FD_ZERO(&readset); FD_SET(pcontrol_fd, &readset); r = rel_select(&readset, TIMEOUT, 0); if (r == 0) { fprintf(stderr, "%s: timeout while waiting for answer from server\n", prgname); exit(RCODE_CERR); } else if (r < 0) { fprintf(stderr, "%s: error while waiting for answer from server: %s\n", prgname, strerror(errno)); exit(RCODE_CERR); } } /*************************************************************************** Get answer from server. ***************************************************************************/ len = recv(pcontrol_fd, answer, sizeof(answer)-1, 0); if (len < 0) { fprintf(stderr, "%s: recv error: %s\n", prgname, strerror(errno)); exit(RCODE_CERR); } answer[len] = '\0'; if (verbosity >= 1) printf("%s\n", answer); /*************************************************************************** If this was an 'show id' command, save it for later reference. ***************************************************************************/ if (! strcasecmp("show id", cmd)) { if (strncmp(answer, "00 set id \"", 11)) { fprintf(stderr, "%s: can't get id from server: got '%s'\n", prgname, answer); exit(RCODE_CERR); } (void) strlcpy(server_type, answer+11, sizeof(server_type)); if (server_type[0] != '\0') server_type[strlen(server_type)-1] = '\0'; } if (answer[0] == '0') return RCODE_OK; return RCODE_ERR; } /***************************************************************************** file_command_loop() Reads all lines from the given file (or stdin if filename == '-'). *****************************************************************************/ int file_command_loop(const char *filename) { char buf[MAXBUF]; FILE *file; int rc=0; int offset=0; if (filename[0] == '-' && filename[1] == '\0') { file = stdin; } else { file = fopen(filename, "r"); if (! file) { fprintf(stderr, "%s: can't open file %s: %s\n", prgname, filename, strerror(errno)); exit(RCODE_CMDLINE); } } while (fgets(buf+offset, sizeof(buf)-offset, file)) { int len = strlen(buf); if (len > 1 && buf[len-2] == '\\') { offset = len-2; continue; } offset = 0; if (buf[0] == '\0' || buf[0] == '#' || buf[1] == '\0') continue; buf[len-1] = '\0'; rc = send_command(buf); if (rc && !ignore) break; } return rc; } /***************************************************************************** tty_command_loop() Reads lines interactively from stdin. Uses readline library if compiled in. Commands starting with a / are interpreted locally, other commands are sent to the server. *****************************************************************************/ #ifndef USE_READLINE char * readline(const char *prompt) { char *ret, *line = malloc(MAXBUF); printf(prompt); fflush(stdout); ret = fgets(line, MAXBUF, stdin); if (! ret) return NULL; if ((ret = strchr(line, '\n'))) *ret = '\0'; return line; } #endif void tty_command_loop() { char hostname[MAXBUF], *p; char prompt[MAXBUF]; char *line; #ifdef USE_READLINE rl_bind_key ('\t', rl_insert); #endif (void)gethostname(hostname, sizeof(hostname)-1); p = strchr(hostname, '.'); if (p) *p = '\0'; snprintf(prompt, sizeof(prompt), PROMPT_TEMPLATE, hostname, server_type); printf("Connection to %s ready. Type '/h' for help, '/q' for quit.\n", server_type); while ((line = readline(prompt))) { #ifdef USE_READLINE if (line[0] != '\0') add_history(line); #endif if (line[0] == '/') { if ((tolower(line[1]) == 'q') && (line[2] == '\0')) { return; } else if ((tolower(line[1]) == 'h') && (line[2] == '\0')) { print_help(server_type, "help"); } else if ((tolower(line[1]) == 'h') && (line[2] == ' ')) { print_help(server_type, line+3); } else { printf("Unknown command\n"); } } else if ((line[0] != '\0') && (line[0] != '#')) { (void) send_command(line); } free(line); } } /***************************************************************************** print_usage() *****************************************************************************/ void print_usage(void) { printf("Usage: %s [OPTION] ... [FILE] ...\n", prgname); printf("Send commands to running pproxy or pserv.\n"); printf("Options:\n"); printf(" -c, --command=CMD send CMD to server\n"); printf(" -s, --socket=FILE open FILE as socket to communicate with server\n"); printf(" -p, --program=PRG open socket to program\n"); printf(" -q, --quiet don't print answer to command\n"); printf(" -v, --verbose print commands as they are sent\n"); printf(" -i, --ignore-errors ignore errors in commands\n"); printf(" --help print this help message\n"); printf(" --version print version information\n"); printf("\n"); printf("If no FILE or if FILE is `-', standard input is read.\n"); printf("If neither the -s nor the -p option is given pcontrol will look for a\n"); printf("socket from pproxy or pserv (in that order) in /var/run/popular.\n"); } /***************************************************************************** main() *****************************************************************************/ int main(int argc, char *argv[]) { int c, rc=0, quiet=0, verbose=0; char *command=NULL; static struct option long_options[] = { { "help", no_argument, 0, 0 }, { "version", no_argument, 0, 0 }, { "command", required_argument, 0, 'c' }, { "socket", required_argument, 0, 's' }, { "program", required_argument, 0, 'p' }, { "quiet", no_argument, 0, 'q' }, { "verbose", no_argument, 0, 'v' }, { "ignore-errors", no_argument, 0, 'i' }, { NULL, 0, 0, 0 } }; prgname = argv[0]; /* program name for error messages */ /*************************************************************************** This program should not be run as root and not be setuid or setgid. ***************************************************************************/ if (!getuid() || !geteuid()) { fprintf(stderr, "%s: don't start this program as `root'.\n", prgname); exit(RCODE_CMDLINE); } if (getuid() != geteuid() || getgid() != getegid()) { fprintf(stderr, "%s: don't make this program setuid or setgid.\n", prgname); exit(RCODE_CMDLINE); } /*************************************************************************** Read command line arguments. ***************************************************************************/ while (1) { int option_index = 0; c = getopt_long(argc, argv, "s:c:p:vqi", long_options, &option_index); if (c == -1) break; switch (c) { case 0: if (! strcmp(long_options[option_index].name, "version")) { printf("pcontrol - POPular POP server suite %s\n", VERSION); exit(RCODE_OK); } else if (! strcmp(long_options[option_index].name, "help")) { print_usage(); exit(RCODE_OK); } else { fprintf(stderr, "%s: unknown option: %s\n", prgname, long_options[option_index].name); exit(RCODE_INTERNAL); } break; case 'c': command = optarg; break; case 'p': if (server_sockname[0]) { fprintf(stderr, "%s: you can't use --socket and --program at the same time (or twice)\n", prgname); exit(RCODE_CMDLINE); } if (!strcmp(optarg, "pserv") || !strcmp(optarg, "serv")) { strlcpy(server_sockname, RUN_DIR "/" PSERV_SOCK_FILE, sizeof(server_sockname)); } else if (!strcmp(optarg, "pproxy") || !strcmp(optarg, "proxy")) { strlcpy(server_sockname, RUN_DIR "/" PPROXY_SOCK_FILE, sizeof(server_sockname)); } else { fprintf(stderr, "%s: unknown program: %s\n", prgname, optarg); exit(RCODE_CMDLINE); } break; case 's': if (server_sockname[0]) { fprintf(stderr, "%s: you can't use --socket and --program at the same time (or twice)\n", prgname); exit(RCODE_CMDLINE); } strlcpy(server_sockname, optarg, sizeof(server_sockname)); break; case 'q': quiet = 1; break; case 'v': verbose = 1; debug = DG_ALL; break; case 'i': ignore = 1; break; default: fprintf(stderr, "Try `%s --help' for more information.\n", prgname); exit(RCODE_CMDLINE); } } if (quiet == 1 && verbose == 1) { fprintf(stderr, "%s: using options --quiet and --verbose together makes no sense.\n", prgname); fprintf(stderr, "Try `%s --help' for more information.\n", prgname); exit(RCODE_CMDLINE); } /*************************************************************************** If no socket and no program name was given on the command line, try to figure it out automatically. ***************************************************************************/ if (! server_sockname[0]) { struct stat sbuf; if (stat(RUN_DIR "/" PPROXY_SOCK_FILE, &sbuf) == 0) { strlcpy(server_sockname, RUN_DIR "/" PPROXY_SOCK_FILE, sizeof(server_sockname)); } else if (stat(RUN_DIR "/" PSERV_SOCK_FILE, &sbuf) == 0) { strlcpy(server_sockname, RUN_DIR "/" PSERV_SOCK_FILE, sizeof(server_sockname)); } else { fprintf(stderr, "%s: no socket file found (is server running? use --socket option?)\n", prgname); exit(RCODE_CMDLINE); } } /*************************************************************************** Prepare socket. ***************************************************************************/ snprintf(client_sockname, sizeof(client_sockname), RUN_DIR "/" PCONTROL_SOCK_FILE, getpid()); pcontrol_fd = uds_socket(client_sockname, PCONTROL_SOCK_UMASK); if (pcontrol_fd < 0) { fprintf(stderr, "%s: socket open failed: %s\n", prgname, strerror(errno)); exit(RCODE_CERR); } atexit(cleanup); /*************************************************************************** Setup signal handling. ***************************************************************************/ { struct sigaction sigAction; memset(&sigAction, 0, sizeof(sigAction)); sigemptyset(&sigAction.sa_mask); sigAction.sa_handler = signal_handler; sigAction.sa_flags = SA_RESTART; if (sigaction(SIGINT, &sigAction, (struct sigaction *)0) != 0) { fprintf(stderr, "%s: sigaction failed", prgname); exit(RCODE_ERR); } } /*************************************************************************** Get id from server. ***************************************************************************/ verbosity = 0; send_command("show id"); verbosity = quiet ? 0 : (verbose ? 2 : 1); /*************************************************************************** Send commands. ***************************************************************************/ if (command) { /* --command */ rc = send_command(command); } else if (optind < argc) { /* files on command line */ int i; for (i=optind; i < argc; i++) { rc = file_command_loop(argv[i]); if (rc && !ignore) break; } } else if (isatty(0)) { /* interactive use */ tty_command_loop(); } else { /* no file, read stdin */ rc = file_command_loop("-"); } exit(rc); } /** THE END *****************************************************************/