pax_global_header00006660000000000000000000000064147720131750014521gustar00rootroot0000000000000052 comment=60e41538e4dea012e82a56e851824ef44a24a1e2 proftpd-mod_msg-0.5.1/000077500000000000000000000000001477201317500146255ustar00rootroot00000000000000proftpd-mod_msg-0.5.1/.gitattributes000066400000000000000000000000621477201317500175160ustar00rootroot00000000000000*.pl linguist-language=C *.pm linguist-language=C proftpd-mod_msg-0.5.1/.github/000077500000000000000000000000001477201317500161655ustar00rootroot00000000000000proftpd-mod_msg-0.5.1/.github/workflows/000077500000000000000000000000001477201317500202225ustar00rootroot00000000000000proftpd-mod_msg-0.5.1/.github/workflows/ci.yml000066400000000000000000000104061477201317500213410ustar00rootroot00000000000000name: CI on: push: branches: - master pull_request: branches: - master schedule: - cron: '11 1 * * 0' jobs: build: runs-on: ubuntu-latest strategy: matrix: compiler: - clang - gcc container: - almalinux:8 - alpine:3.18 - ubuntu:22.04 container: ${{ matrix.container }} steps: - name: Checkout ProFTPD uses: actions/checkout@v3 with: repository: proftpd/proftpd path: proftpd - name: Checkout module source code uses: actions/checkout@v3 with: path: proftpd-mod_msg - name: Whitespace check if: ${{ matrix.container == 'ubuntu:22.04' }} run: | apt-get update -qq apt-get install -y git cd proftpd-mod_msg if [[ -n $(git diff --check HEAD^) ]]; then echo "You must remove whitespace before submitting a pull request" echo "" git diff --check HEAD^ exit 1 fi - name: Prepare module source code run: | cp proftpd-mod_msg/mod_msg.c proftpd/contrib/ - name: Install Alpine packages if: ${{ matrix.container == 'alpine:3.18' }} run: | apk update # for builds apk add bash build-base clang compiler-rt gcc make zlib-dev # for unit tests apk add check check-dev subunit subunit-dev # for debugging clang --version gcc --version - name: Install RPM packages if: ${{ matrix.container == 'almalinux:8' }} run: | # Need to add other repos for e.g. libsodium yum install -y dnf-plugins-core epel-release clang gcc make zlib-devel # for unit tests yum install -y check-devel https://cbs.centos.org/kojifiles/packages/subunit/1.4.0/1.el8/x86_64/subunit-1.4.0-1.el8.x86_64.rpm https://cbs.centos.org/kojifiles/packages/subunit/1.4.0/1.el8/x86_64/subunit-devel-1.4.0-1.el8.x86_64.rpm # for debugging clang --version gcc --version - name: Install Ubuntu packages if: ${{ matrix.container == 'ubuntu:22.04' }} run: | apt-get update -qq # for builds apt-get install -y clang gcc make # for unit tests apt-get install -y check libsubunit-dev # for integration/regression test apt-get install -y \ libdata-dumper-simple-perl \ libdatetime-perl \ libfile-copy-recursive-perl \ libfile-path-tiny-perl \ libfile-spec-native-perl \ libnet-inet6glue-perl \ libnet-ssh2-perl \ libnet-ssleay-perl \ libnet-telnet-perl \ libposix-2008-perl \ libtest-unit-perl \ libtime-hr-perl \ libwww-perl PERL_MM_USE_DEFAULT=1 perl -MCPAN -e 'install Net::FTPSSL' # for test code coverage apt-get install -y lcov ruby gem install coveralls-lcov # for HTML validation apt-get install -y tidy # for debugging clang --version gcc --version - name: Prepare code coverage if: ${{ matrix.container == 'ubuntu:22.04' }} run: | lcov --directory proftpd --zerocounters - name: Build as static module env: CC: ${{ matrix.compiler }} run: | cd proftpd ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-ctrls --enable-devel=coverage --enable-tests --with-modules=mod_msg make - name: Install as static module run: | cd proftpd make install - name: Build as shared module env: CC: ${{ matrix.compiler }} run: | cd proftpd make clean ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-ctrls --enable-devel --enable-dso --with-shared=mod_msg make - name: Install as shared module run: | cd proftpd make install - name: Check HTML docs run: | cd proftpd-mod_msg echo "Processing mod_msg.html" tidy -errors -omit -q mod_msg.html | exit 0 proftpd-mod_msg-0.5.1/.gitignore000066400000000000000000000000361477201317500166140ustar00rootroot00000000000000*.lo *~ *.swo *.swp tests.log proftpd-mod_msg-0.5.1/README.md000066400000000000000000000011721477201317500161050ustar00rootroot00000000000000proftpd-mod_msg =============== Status ------ [![GitHub Actions CI Status](https://github.com/Castaglia/proftpd-mod_msg/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Castaglia/proftpd-mod_msg/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/license-GPL-brightgreen.svg)](https://img.shields.io/badge/license-GPL-brightgreen.svg) Synopsis -------- The `mod_msg` module for ProFTPD supports sending messages to connected FTP clients. For further module documentation, see [mod_msg.html](https://htmlpreview.github.io/?https://github.com/Castaglia/proftpd-mod_msg/blob/master/mod_msg.html). proftpd-mod_msg-0.5.1/mod_msg.c000066400000000000000000000572011477201317500164230ustar00rootroot00000000000000/* * ProFTPD: mod_msg -- a module for sending messages to connected clients * Copyright (c) 2004-2025 TJ Saunders * * 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. * * This is mod_msg, contrib software for proftpd 1.3.x and above. * For more information contact TJ Saunders . */ #include "conf.h" #include "mod_ctrls.h" #include "privs.h" #include #include #ifndef MSGMAX # define MSGMAX 8192 #endif /* MSGMAX */ #define MOD_MSG_VERSION "mod_msg/0.5.1" /* Make sure the version of proftpd is as necessary. */ #if PROFTPD_VERSION_NUMBER < 0x0001030604 # error "ProFTPD 1.3.6 or later required" #endif #define MSG_PROJ_ID 246 /* From src/main.c */ extern pid_t mpid; module msg_module; #if !defined(PR_USE_CTRLS) # error "mod_msg requires Controls support (--enable-ctrls)" #endif /* PR_USE_CTRLS */ static ctrls_acttab_t msg_acttab[]; static int msg_engine = FALSE; static int msg_logfd = -1; static array_header *msg_pending_list = NULL; static pool *msg_pool = NULL; static pool *msg_pending_pool = NULL; static pr_fh_t *msg_queue_fh = NULL; static char *msg_queue_path = NULL; static int msg_qid = -1; /* Define our own structure for messages, since one is not portably defined. */ struct mq_msg { /* Message type */ long mtype; /* Message data */ char mtext[1]; }; static key_t msg_get_key(const char *path) { pr_fh_t *fh; /* ftok() uses stat(2) on the given path, which means that it needs to exist. * So stat() the file ourselves first, and create it if necessary. We need * make sure that permissions on the file we create match the ones that * mod_xfer would create. */ fh = pr_fsio_open(path, O_WRONLY|O_CREAT); if (!fh) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error opening '%s': %s", path, strerror(errno)); return -1; } pr_fsio_close(fh); return ftok(path, MSG_PROJ_ID); } static int msg_get_queue(const char *path) { int qid; /* Obtain a key for this path. */ key_t key = msg_get_key(path); if (key == (key_t) -1) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "unable to get key for '%s': %s", path, strerror(errno)); return -1; } /* Try first using IPC_CREAT|IPC_EXCL, to check if there is an existing * queue for this key. If there is, try again, using a flag of zero. */ qid = msgget(key, IPC_CREAT|IPC_EXCL|0666); if (qid < 0) { if (errno == EEXIST) qid = msgget(key, 0); else return -1; } return qid; } static int msg_recv_msg(void) { int nmsgs = 0; ssize_t msglen = 0; char buf[MSGMAX] = {'\0'}; struct mq_msg *msg; if (!msg_pending_list) { if (!msg_pending_pool) { msg_pending_pool = make_sub_pool(msg_pool); pr_pool_tag(msg_pending_pool, MOD_MSG_VERSION ": pending pool"); } msg_pending_list = make_array(msg_pending_pool, 0, sizeof(char *)); } msg = malloc(sizeof(struct mq_msg) + MSGMAX - sizeof(msg->mtext)); if (msg == NULL) { pr_session_end(0); } msglen = msgrcv(msg_qid, msg, sizeof(buf), getpid(), IPC_NOWAIT|MSG_NOERROR); while (msglen > 0) { pr_signals_handle(); /* msglen is the number of bytes in the message. This means it does * not know of string semantics, hence we need to add one byte for the * terminating NUL character. */ *((char **) push_array(msg_pending_list)) = pstrndup(msg_pending_pool, msg->mtext, msglen + 1); nmsgs++; msglen = msgrcv(msg_qid, msg, sizeof(buf), getpid(), IPC_NOWAIT|MSG_NOERROR); } free(msg); if (msglen < 0 && #ifdef ENOMSG errno != ENOMSG && #endif /* ENOMSG */ errno != EAGAIN) { return -1; } return nmsgs; } static int msg_send_msg(pid_t dst_pid, const char *msgstr) { int res; struct mq_msg *msg; /* Take the terminating NUL into account. */ size_t msglen = strlen(msgstr) + 1; msg = malloc(sizeof(struct mq_msg) + MSGMAX - sizeof(msg->mtext)); if (msg == NULL) { pr_session_end(0); } msg->mtype = dst_pid; sstrncpy(msg->mtext, msgstr, msglen); while (msgsnd(msg_qid, msg, msglen, IPC_NOWAIT) < 0) { pr_signals_handle(); if (errno != EAGAIN) { free(msg); return -1; } } free(msg); /* Send SIGUSR2 to the destination process, to let it know that it should * check the queue for messages. */ PRIVS_ROOT res = kill(dst_pid, SIGUSR2); PRIVS_RELINQUISH if (res < 0) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error sending notice: %s", strerror(errno)); } return 0; } /* Configuration handlers */ /* usage: MessageControlsACLs actions|all allow|deny user|group list */ MODRET set_msgctrlsacls(cmd_rec *cmd) { char *bad_action = NULL, **actions = NULL; CHECK_ARGS(cmd, 4); CHECK_CONF(cmd, CONF_ROOT); /* We can cheat here, and use the ctrls_parse_acl() routine to * separate the given string... */ actions = pr_ctrls_parse_acl(cmd->tmp_pool, cmd->argv[1]); /* Check the second parameter to make sure it is "allow" or "deny" */ if (strcmp(cmd->argv[2], "allow") != 0 && strcmp(cmd->argv[2], "deny") != 0) { CONF_ERROR(cmd, "second parameter must be 'allow' or 'deny'"); } /* Check the third parameter to make sure it is "user" or "group" */ if (strcmp(cmd->argv[3], "user") != 0 && strcmp(cmd->argv[3], "group") != 0) { CONF_ERROR(cmd, "third parameter must be 'user' or 'group'"); } bad_action = pr_ctrls_set_module_acls(msg_acttab, msg_pool, actions, cmd->argv[2], cmd->argv[3], cmd->argv[4]); if (bad_action != NULL) { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown action: '", bad_action, "'", NULL)); } return PR_HANDLED(cmd); } /* usage: MessageEngine on|off */ MODRET set_msgengine(cmd_rec *cmd) { int engine = -1; config_rec *c; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); engine = get_boolean(cmd, 1); if (engine == -1) { CONF_ERROR(cmd, "expected Boolean parameter"); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = engine; return PR_HANDLED(cmd); } /* usage: MessageLog path */ MODRET set_msglog(cmd_rec *cmd) { CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT); if (pr_fs_valid_path(cmd->argv[1]) < 0) { CONF_ERROR(cmd, "must be an absolute path"); } add_config_param_str(cmd->argv[0], 1, cmd->argv[1]); return PR_HANDLED(cmd); } /* usage: MessageQueue path */ MODRET set_msgqueue(cmd_rec *cmd) { CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT); if (pr_fs_valid_path(cmd->argv[1]) < 0) { CONF_ERROR(cmd, "must be an absolute path"); } msg_queue_path = pstrdup(msg_pool, cmd->argv[1]); return PR_HANDLED(cmd); } /* Command handlers */ MODRET msg_post_any(cmd_rec *cmd) { register unsigned int i = 0; char **msgs; if (msg_engine == FALSE) { return PR_DECLINED(cmd); } /* If there are no messages pending for this process, be done now. */ if (msg_pending_list == NULL || msg_pending_list->nelts == 0) { return PR_DECLINED(cmd); } /* Skip commands whose reply format is strictly proscribed. */ /* XXX there are probably more commands to be skipped here */ if (strcmp(cmd->argv[0], C_EPSV) == 0 || strcmp(cmd->argv[0], C_PASV) == 0 || strcmp(cmd->argv[0], C_STOU) == 0) { return PR_DECLINED(cmd); } /* Tack on any messages to this command. */ msgs = (char **) msg_pending_list->elts; for (i = 0; i < msg_pending_list->nelts; i++) { pr_response_add(R_DUP, "%s", msgs[i]); } /* Clear the pending pool. */ destroy_pool(msg_pending_pool); msg_pending_pool = NULL; msg_pending_list = NULL; return PR_DECLINED(cmd); } MODRET msg_post_err_any(cmd_rec *cmd) { register unsigned int i = 0; char **msgs; if (msg_engine == FALSE) { return PR_DECLINED(cmd); } /* If there are no messages pending for this process, be done now. */ if (msg_pending_list == NULL || msg_pending_list->nelts == 0) { return PR_DECLINED(cmd); } /* Skip commands whose reply format is strictly proscribed. */ /* XXX there are probably more commands to be skipped here */ if (strcmp(cmd->argv[0], C_EPSV) == 0 || strcmp(cmd->argv[0], C_PASV) == 0 || strcmp(cmd->argv[0], C_STOU) == 0) { return PR_DECLINED(cmd); } /* Tack on any messages to this command. */ msgs = (char **) msg_pending_list->elts; for (i = 0; i < msg_pending_list->nelts; i++) { pr_response_add_err(R_DUP, "%s", msgs[i]); } /* Clear the pending pool. */ destroy_pool(msg_pending_pool); msg_pending_pool = NULL; msg_pending_list = NULL; return PR_DECLINED(cmd); } /* Event handlers */ static void msg_exit_ev(const void *event_data, void *user_data) { /* Remove the queue from the system. We can only do this reliably * when the standalone daemon process exits; if it's an inetd process, * there may be other proftpd processes still running. */ if (getpid() == mpid && ServerType == SERVER_STANDALONE) { struct msqid_ds ds; if (msgctl(msg_qid, IPC_RMID, &ds) < 0 && errno != EINVAL) { pr_log_debug(DEBUG1, MOD_MSG_VERSION ": error removing queue %d: %s", msg_qid, strerror(errno)); } } } static void msg_postparse_ev(const void *event_data, void *user_data) { config_rec *c; /* Open the MessageLog for the "server config" server here, if any, for * use for logging by the daemon process. */ c = find_config(main_server->conf, CONF_PARAM, "MessageLog", FALSE); if (c != NULL) { const char *path = c->argv[0]; if (strcasecmp(path, "none") != 0 && pr_log_openfile(path, &msg_logfd, 0660) < 0) { pr_log_debug(DEBUG2, MOD_MSG_VERSION ": error opening MessageLog '%s': %s", path, strerror(errno)); msg_logfd = -1; } } if (msg_queue_path != NULL) { msg_queue_fh = pr_fsio_open(msg_queue_path, O_RDWR|O_CREAT); } else { errno = EINVAL; } if (msg_queue_fh == NULL) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error opening MessageQueue: %s", strerror(errno)); } else { msg_qid = msg_get_queue(msg_queue_path); if (msg_qid < 0) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error obtaining queue ID: %s", strerror(errno)); } else { pr_log_debug(DEBUG0, MOD_MSG_VERSION ": obtained queue ID %d", msg_qid); } } } static void msg_restart_ev(const void *event_data, void *user_data) { register unsigned int i; if (msg_pool != NULL) { destroy_pool(msg_pool); } msg_pool = make_sub_pool(permanent_pool); pr_pool_tag(msg_pool, MOD_MSG_VERSION); for (i = 0; msg_acttab[i].act_action; i++) { msg_acttab[i].act_acl = pcalloc(msg_pool, sizeof(ctrls_acl_t)); pr_ctrls_init_acl(msg_acttab[i].act_acl); } } static void msg_sigusr2_ev(const void *event_data, void *user_data) { /* Check the queue for any messages for us. */ int res = msg_recv_msg(); switch (res) { case -1: (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error receiving messages for pid %u: %s", getpid(), strerror(errno)); break; case 0: (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "received notice, no messages for pid %u", getpid()); break; default: (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "received notice, read in %d %s for pid %u", res, res == 1 ? "message" : "messages", getpid()); } } static void msg_startup_ev(const void *event_data, void *user_data) { int res, xerrno; /* Make sure the process has an fd to the scoreboard. */ PRIVS_ROOT res = pr_open_scoreboard(O_RDWR); xerrno = errno; PRIVS_RELINQUISH if (res < 0) { switch (res) { case PR_SCORE_ERR_BAD_MAGIC: pr_log_debug(DEBUG0, "error opening scoreboard: bad/corrupted file"); break; case PR_SCORE_ERR_OLDER_VERSION: pr_log_debug(DEBUG0, "error opening scoreboard: bad version (too old)"); break; case PR_SCORE_ERR_NEWER_VERSION: pr_log_debug(DEBUG0, "error opening scoreboard: bad version (too new)"); break; default: pr_log_debug(DEBUG0, "error opening scoreboard: %s", strerror(xerrno)); break; } } } /* Control handlers */ /* Handle the 'msg' action */ static int msg_handle_msg(pr_ctrls_t *ctrl, int reqargc, char **reqargv) { int res = 0, msg_errno = 0, msg_know_dst = FALSE, msg_sent = FALSE; if (!pr_ctrls_check_acl(ctrl, msg_acttab, "msg")) { pr_ctrls_add_response(ctrl, "access denied"); return -1; } /* Sanity check */ if (reqargc == 0 || reqargv == NULL) { pr_ctrls_add_response(ctrl, "missing required parameters"); return -1; } /* Handle 'msg user' requests. */ if (strcmp(reqargv[0], "user") == 0) { register int i = 0; pr_scoreboard_entry_t *score = NULL; const char *user, *msgstr = ""; size_t msglen; if (reqargc == 1) { pr_ctrls_add_response(ctrl, "msg user: missing required user name"); return -1; } if (reqargc == 2) { pr_ctrls_add_response(ctrl, "msg user: missing required message"); return -1; } user = reqargv[1]; /* Concatenate the given message into a single string. There may need to * be a maximum length on this strength, depending on the maximum msg * size allowed for SysV message queues. */ for (i = 2; i < reqargc; i++) { msgstr = pstrcat(ctrl->ctrls_tmp_pool, msgstr, *msgstr ? " " : "", reqargv[i], NULL); } msglen = strlen(msgstr) + 1; if (msglen == 0) { pr_ctrls_add_response(ctrl, "zero length message not allowed"); return -1; } if (msglen >= MSGMAX) { pr_ctrls_add_response(ctrl, "message exceeds maximum length (%u). " "Try sending smaller messages", MSGMAX); return -1; } /* Iterate through the scoreboard, looking for any sessions for the * given user. */ if (pr_rewind_scoreboard() < 0) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error rewinding scoreboard: %s", strerror(errno)); } while ((score = pr_scoreboard_entry_read()) != NULL) { pr_signals_handle(); if (strcmp(user, score->sce_user) == 0) { msg_know_dst = TRUE; if (msg_send_msg(score->sce_pid, msgstr) < 0) { msg_errno = errno; (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error sending message to user '%s' (pid %u): %s", user, score->sce_pid, strerror(errno)); } else { msg_sent = TRUE; } } } pr_restore_scoreboard(); /* Handle 'msg host' requests. */ } else if (strcmp(reqargv[0], "host") == 0) { register int i = 0; pr_scoreboard_entry_t *score = NULL; const char *addr, *msgstr = ""; const pr_netaddr_t *na; if (reqargc == 1) { pr_ctrls_add_response(ctrl, "msg host: missing required host name"); return -1; } if (reqargc == 2) { pr_ctrls_add_response(ctrl, "msg host: missing required message"); return -1; } /* Concatenate the given message into a single string. There may need to * be a maximum length on this strength, depending on the maximum msg * size allowed for SysV message queues. */ for (i = 2; i < reqargc; i++) { msgstr = pstrcat(ctrl->ctrls_tmp_pool, msgstr, *msgstr ? " " : "", reqargv[i], NULL); } if (strlen(msgstr) >= MSGMAX) { pr_ctrls_add_response(ctrl, "message exceeds maximum length (%u). " "Try sending smaller messages", MSGMAX); return -1; } na = pr_netaddr_get_addr(ctrl->ctrls_tmp_pool, reqargv[1], NULL); if (na == NULL) { pr_ctrls_add_response(ctrl, "msg host: error resolving '%s': %s", reqargv[1], strerror(errno)); return -1; } addr = pr_netaddr_get_ipstr(na); /* Iterate through the scoreboard, looking for any sessions for the * given address. */ if (pr_rewind_scoreboard() < 0) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error rewinding scoreboard: %s", strerror(errno)); } while ((score = pr_scoreboard_entry_read()) != NULL) { pr_signals_handle(); if (strcmp(addr, score->sce_client_addr) == 0) { msg_know_dst = TRUE; if (msg_send_msg(score->sce_pid, msgstr) < 0) { msg_errno = errno; (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error sending message to host '%s' (pid %u): %s", reqargv[1], score->sce_pid, strerror(errno)); } else { msg_sent = TRUE; } } } pr_restore_scoreboard(); /* Handle 'msg class' requests. */ } else if (strcmp(reqargv[0], "class") == 0) { register int i = 0; pr_scoreboard_entry_t *score; const char *class = reqargv[1], *msgstr = ""; if (reqargc == 1) { pr_ctrls_add_response(ctrl, "msg class: missing required class name"); return -1; } if (reqargc == 2) { pr_ctrls_add_response(ctrl, "msg class: missing required message"); return -1; } /* Concatenate the given message into a single string. There may need to * be a maximum length on this strength, depending on the maximum msg * size allowed for SysV message queues. */ for (i = 2; i < reqargc; i++) { msgstr = pstrcat(ctrl->ctrls_tmp_pool, msgstr, *msgstr ? " " : "", reqargv[i], NULL); } if (strlen(msgstr) >= MSGMAX) { pr_ctrls_add_response(ctrl, "message exceeds maximum length (%u). " "Try sending smaller messages", MSGMAX); return -1; } if (pr_rewind_scoreboard() < 0) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error rewinding scoreboard: %s", strerror(errno)); } while ((score = pr_scoreboard_entry_read()) != NULL) { pr_signals_handle(); if (strcmp(score->sce_class, class) == 0) { msg_know_dst = TRUE; if (msg_send_msg(score->sce_pid, msgstr) < 0) { msg_errno = errno; (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error sending message to class '%s' (pid %u): %s", reqargv[1], score->sce_pid, strerror(errno)); } else { msg_sent = TRUE; } } } pr_restore_scoreboard(); /* Handle 'msg all' requests. */ } else if (strcmp(reqargv[0], "all") == 0) { register int i = 0; pr_scoreboard_entry_t *score; const char *msgstr = ""; if (reqargc == 1) { pr_ctrls_add_response(ctrl, "msg all: missing required message"); return -1; } /* Concatenate the given message into a single string. There may need to * be a maximum length on this strength, depending on the maximum msg * size allowed for SysV message queues. */ for (i = 1; i < reqargc; i++) { msgstr = pstrcat(ctrl->ctrls_tmp_pool, msgstr, *msgstr ? " " : "", reqargv[i], NULL); } if (strlen(msgstr) >= MSGMAX) { pr_ctrls_add_response(ctrl, "message exceeds maximum length (%u). " "Try sending smaller messages", MSGMAX); return -1; } if (pr_rewind_scoreboard() < 0) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error rewinding scoreboard: %s", strerror(errno)); } msg_know_dst = TRUE; while ((score = pr_scoreboard_entry_read()) != NULL) { pr_signals_handle(); if (msg_send_msg(score->sce_pid, msgstr) < 0) { msg_errno = errno; (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "error sending message to all (pid %lu): %s", (unsigned long) score->sce_pid, strerror(errno)); } else { msg_sent = TRUE; } } pr_restore_scoreboard(); } else { pr_ctrls_add_response(ctrl, "unknown msg type requested: '%s'", reqargv[0]); return -1; } if (msg_sent == TRUE) { pr_ctrls_add_response(ctrl, "message sent"); } else if (msg_know_dst == FALSE) { pr_ctrls_add_response(ctrl, "unable to send message: " "no such client connected"); } else { pr_ctrls_add_response(ctrl, "error sending message: %s", strerror(msg_errno)); } return res; } /* Initialization functions */ static int msg_init(void) { register unsigned int i; msg_pool = make_sub_pool(permanent_pool); pr_pool_tag(msg_pool, MOD_MSG_VERSION); for (i = 0; msg_acttab[i].act_action; i++) { msg_acttab[i].act_acl = pcalloc(msg_pool, sizeof(ctrls_acl_t)); pr_ctrls_init_acl(msg_acttab[i].act_acl); if (pr_ctrls_register(&msg_module, msg_acttab[i].act_action, msg_acttab[i].act_desc, msg_acttab[i].act_cb) < 0) { pr_log_pri(PR_LOG_INFO, MOD_MSG_VERSION ": error registering '%s' control: %s", msg_acttab[i].act_action, strerror(errno)); } } pr_event_register(&msg_module, "core.exit", msg_exit_ev, NULL); pr_event_register(&msg_module, "core.postparse", msg_postparse_ev, NULL); pr_event_register(&msg_module, "core.restart", msg_restart_ev, NULL); pr_event_register(&msg_module, "core.startup", msg_startup_ev, NULL); return 0; } static int msg_sess_init(void) { config_rec *c; /* If there was an error opening the MessageQueue, force the module to * be inoperative. We'd much rather not operate without the MessageQueue. */ if (msg_queue_fh == NULL) { msg_engine = FALSE; (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "missing required MessageQueue, disabling module"); return 0; } /* If we don't have the qid, it's pointless to continue further. */ if (msg_qid < 0) { (void) pr_log_writefile(msg_logfd, MOD_MSG_VERSION, "missing required queue ID, disabling module"); return 0; } c = find_config(main_server->conf, CONF_PARAM, "MessageEngine", FALSE); if (c != NULL) { unsigned char engine; engine = *((unsigned char *) c->argv[0]); msg_engine = engine; } if (msg_engine == FALSE) { return 0; } pr_event_register(&msg_module, "core.signal.USR2", msg_sigusr2_ev, NULL); pr_event_unregister(&msg_module, "core.exit", msg_exit_ev); return 0; } static ctrls_acttab_t msg_acttab[] = { { "msg", "send messages to connected clients", NULL, msg_handle_msg}, { NULL, NULL, NULL, NULL } }; /* Module API tables */ static conftable msg_conftab[] = { { "MessageControlsACLs", set_msgctrlsacls, NULL }, { "MessageEngine", set_msgengine, NULL }, { "MessageLog", set_msglog, NULL }, { "MessageQueue", set_msgqueue, NULL }, { NULL } }; static cmdtable msg_cmdtab[] = { { POST_CMD, C_ANY, G_NONE, msg_post_any, FALSE, FALSE }, { POST_CMD_ERR, C_ANY, G_NONE, msg_post_err_any, FALSE, FALSE }, { 0, NULL } }; module msg_module = { NULL, NULL, /* Module API version 2.0 */ 0x20, /* Module name */ "msg", /* Module configuration handler table */ msg_conftab, /* Module command handler table */ msg_cmdtab, /* Module authentication handler table */ NULL, /* Module initialization function */ msg_init, /* Session initialization function */ msg_sess_init }; proftpd-mod_msg-0.5.1/mod_msg.html000066400000000000000000000133161477201317500171440ustar00rootroot00000000000000 ProFTPD module mod_msg

ProFTPD module mod_msg



The mod_msg module allows system users to send messages to connected clients via the ftpdctl program. The module works by creating a SysV message queue, which is used to pass messages from the daemon process to session processes.

This module is contained in the mod_msg.c file for ProFTPD 1.2, and is not compiled by default. Installation instructions are discussed here. Documentation on mod_msg usage follows.

The most current version of mod_msg can be found at:

  http://www.castaglia.org/proftpd/

Author

Please contact TJ Saunders <tj at castaglia.org> with any questions, concerns, or suggestions regarding this module.

Directives

Control Actions


MessageControlsACLs

Syntax: MessageControlsACLs actions|"all" "allow"|"deny" "user"|"group" list
Default: None
Context: server config Module: mod_msg
Compatibility: 1.2.10rc1 and later

Example:

  # Allow all users to send messages to connected clients
  MessageControlsACLs msg allow user *


MessageEngine

Syntax: MessageEngine on|off
Default: off
Context: server config, <VirtualHost>, <Global>
Module: mod_msg
Compatibility: 1.2.10rc1 and later

The MessageEngine directive enables or disables the module's runtime message queue. If it is set to off this module does no passing of messages. Use this directive to disable the module instead of commenting out all mod_msg directives.


MessageLog

Syntax: MessageLog path|"none"
Default: None
Context: server config Module: mod_msg
Compatibility: 1.2.10rc1 and later

The MessageLog directive is used to a specify a log file for mod_msg reporting and debugging. The path parameter must be the full path to the file to use for logging. Note that this path must not be to a world-writeable directory and, unless AllowLogSymlinks is explicitly set to on (generally a bad idea), the path must not be a symbolic link.

If path is "none", no logging will be done at all.


MessageQueue

Syntax: MessageQueue path
Default: None
Context: server config Module: mod_msg
Compatibility: 1.2.10rc1 and later

The MessageQueue directive configures a path to a file that mod_msg will use when sending messages. This file itself is not used for storing messages; it is necessary for obtaining a unique key to use as the ID for the message queue.


Control Actions


msg

Syntax: ftpdctl msg [class|host|user name]|[all] message
Purpose: Send a message to a class, host, user, or all

The msg control action can be used to send messages to connected clients. Any words that follow the given user or host name in the ftpdctl command will be sent to the connected session, and added to the response to the session's next FTP command. Note, however, that some command responses cannot be altered, such as EPSV, PASV, or STOU; mod_msg avoids these command responses. Note: some FTP clients may not display the text messages of FTP responses to the user; there is nothing that mod_msg can do when such clients are used.

Examples:

  ftpdctl msg user dave Dave, you need to delete some files
  ftpdctl msg all This server will be going offline for maintenance soon


Installation

To install mod_msg, copy the mod_msg.c file into
  proftpd-dir/contrib/
after unpacking the latest proftpd-1.2 source code. Then follow the usual steps for using third-party modules in proftpd, making sure to include the --enable-ctrls configure option, which mod_msg requires:
  ./configure --enable-ctrls --with-modules=mod_msg
  make
  make install


Usage

Example configuration:

  <IfModule mod_msg.c>
    MessageEngine on
    MessageLog /var/log/ftpd/msg.log
    MessageQueue /var/ftpd/msg.queue

    # Allow all system users to send messages
    MessageControlsACLs msg allow user *
  </IfModule>


© Copyright 2017 TJ Saunders
All Rights Reserved