Synchronising Voicemail Folders

@InterLinked

You might be right. :slight_smile:

When I left last night, I poured myself a glass of red-wine, and since the other half was hogging the TV came back up into my home office.

I thought… “lets just have a poke around in the asterisk source code”, no real idea what I was doing, but a few glasses later, and looking at the clock to realise it was 2 am…

Now I have an understanding of how to write a “function” for asterisk, and now realise I can actually do this in the scope given for the project (Previously I thought it was going to take me a month or two to learn my way round the asterisk code base, hence why initially it was a no).

The moral of the story? - A good bottle of red usually solves these kinds of queries :joy::joy::joy:

Now I just need to dust off my plain old C skills that I’ve not used for a very long time, and break out my Analogue version of Google…

Despite the title “beginning” it’s actually a really useful and handy reference guide to file system and other functions.

Looks like you added a dialplan function, but an AMI action might be more appropriate for your use case.

… and . indicate current directory and above directory so you’ll want to manually skip those in your loop when you iterate over the directory.

Finding an existing example that does something similar and going from there is always a great way to get started.

The message metadata is stored in text files so you should be able to get the info you need from there (if you want info like the message length).

If you are planning on adding a new action or function, make sure to sign your Asterisk CLA and contribute it back to the project :slight_smile:

I intend to share it back to the project, but I don’t have time to maintain it, so I’ll happily hand the code to someone else who can…

I have enough OSS projects that I’m already involved within the .NET & C# worlds :slight_smile: I really don’t think I have bandwidth for any more.

Let me get things working the way I think they need too first however, then we’ll take it from there.

Cheers
Shawty

PS: please do advise which files I should look in for an AMI action?

Yes I wrote this as a function, and that right now seems to work both for dialplan and getvar actions, which accounts for easily most of what I need to do.

Well once I iron out the bugs I have anyway :slight_smile:

PPS: this is what I have for the folders list so far …

cat func_vmfolders.c
/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 2007, Digium, Inc.
 *
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2. See the LICENSE file
 * at the top of the source tree.
 */

/*! \file
 *
 * VMFOLDERS function that given a mailbox number will return a comma seperated list of physical folders for that mailbox.
 *
 * \note hacked together by butchering func_sysinfo
 *
 * \author Peter Shaw/!Shawty!-DS
 *
 * \ingroup functions
 */

/*** MODULEINFO
        <support_level>core</support_level>
 ***/

#include <dirent.h>
#include <string.h>
#include <sys/stat.h>

#include "asterisk.h"

#include "asterisk/module.h"
#include "asterisk/pbx.h"

static int vmfolders_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
  ast_log(LOG_WARNING, "Starting VMFOLDERS (warning)\n");
  ast_log(LOG_ERROR, "Starting VMFOLDERS (error)\n");
  ast_log(LOG_DEBUG, "Starting VMFOLDERS (debug)\n");
  ast_log(LOG_NOTICE, "Starting VMFOLDERS (notice)\n");

  char *baseFolder = "/var/spool/asterisk/voicemail";

  char folderList[2048]; // Max 2k should be enough for the return value!

  DIR *d;
  struct dirent *dir;
  struct stat statbuf;

  char folderToQuery[512];

  d = opendir(folderToQuery);
  if(d == NULL) {
    //snprintf(buf, len, "");
    return 2; // Just an experiment to see if this value is returned in asterisk.
  }

  sprintf(folderToQuery, "%s%s", baseFolder, "/default/103"); // TODO: parse this from the parameter

  chdir(folderToQuery);
  while((dir = readdir(d)) != NULL) {

    lstat(dir->d_name, &statbuf);
    if(S_ISDIR(statbuf.st_mode)) { // We only want directorys, not files

      if(strcmp(".", dir->d_name) == 0 || strcmp("..", dir->d_name) == 0) continue; // we dont want .. or . dir names

      strcat(folderList, dir->d_name);
      strcat(folderList, ",");

    }

  }

  closedir(d);

  snprintf(buf, len, "%s", folderList);

  return 0;
}

/*** DOCUMENTATION
        <function name="VMFOLDERS" language="en_US">
                <synopsis>
                        Returns a comma seperated list of physical filesystem folders in the provided mailbox.
                </synopsis>
                <syntax>
                        <parameter name="vmmailbox" required="true">
                          <para>The mailbox number of the mailbox to query in standard xxx@context format, if no '@context' is provided 'default' is assumed, only ONE mailbox at a time can be queried.</para>
                        </parameter>
                </syntax>
                <description>
                        <para>Queries the physical file system on the asterisk server to get a list of folders in a users mailbox thus allowing a dialplan to find out if there are any folders
                         used by the mailbox other than the standard 'INBOX', "Old' and 'Urg' folders created by default.</para>
                        <para>The function returns a comma delimited string something like: "INBOX,Urg,Old", or an empty string if no folders where found. No folders found, doesn't mean the mailbox
                         does not exist however.  A new mailbox that has not had any messages left for it will have an empty file system until a message is saved, so do not use this as a method
                         to check if a mailbox exists, there are other functions for that.
                </description>
        </function>
 ***/

static struct ast_custom_function vmfolders_function = {
        .name = "VMFOLDERS",
        .read = vmfolders_helper,
        .read_max = 2048,
};

static int unload_module(void)
{
        return ast_custom_function_unregister(&vmfolders_function);
}

static int load_module(void)
{
        return ast_custom_function_register(&vmfolders_function);
}

AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "VmFolders function ");

Good start! At some point, you’ll want to bring this up with the coding guidelines: Coding Guidelines - Asterisk Project - Asterisk Project Wiki

(no C99 comments, spacing, indentation, etc.)

Also personally, I think VM_FOLDERS might work better than VMFOLDERS (I know, there’s one of each way at the moment but things seem to be trending this direction)

I intend to share it back to the project, but I don’t have time to maintain it, so I’ll happily hand the code to someone else who can…

You’d be the copyright holder so you’d need to sign the CLA and upload the patch or comment that you approve of its being included by the terms of the Asterisk CLA.

Once it’s merged, there’s no “ongoing maintenance” required, just the work to get it merged.

PS: please do advise which files I should look in for an AMI action?

Not sure what you mean by this exactly?

Yes I wrote this as a function, and that right now seems to work both for dialplan and getvar actions, which accounts for easily most of what I need to do.

Using Getvar to get this might work (although I’m not sure knowing what folders tells you), but not if you want to get the actual message metadata. There’s no sane way to do that with a dialplan function - you definitely need that to be a native AMI event.

Take a look at the manager_list_voicemail_users function in app_voicemail.c. That should give you an idea as to how to make an AMI action/response. You can copy it and start hacking/modifying at it to do whatever you want. Don’t forget to register/unregister it at module load/unload with the other manager stuff.

1 Like

Which files can I use as a template to make a manager action (I butchered func_sysinfo.c to make the function) similar and easy to understand one for AMI?

Which files can I use as a template to make a manager action (I butchered func_sysinfo.c to make the function) similar and easy to understand one for AMI?

app_voicemail contains manager actions - look at the function I suggested earlier, for example.

AMI events and actions are not standalone, unlike dialplan apps or functions, they are typically embedded into some larger module to provide AMI integration.

ahhh gotcha. SO in my case, I could write functions for folder list, file list and message details, then embed an asterisk manager action into those same code files…

I think the normal place would be in the voicemail application module.

So just a little update.

I was mucking around with different manager actions, trying to get a feel for what the different return types look like, and I was playing around with the

“GetConfig”

Manager command.

Not expecting anything to happen, I made the “Filename” header a full path and name to a VPS system message file, and lo and behold…

Action: GetConfig
Filename: /var/spool/asterisk/voicemail/default/103/INBOX/msg0000.txt

Response: Success
Category-000000: message
Line-000000-000000: origmailbox=103
Line-000000-000001: context=internal
Line-000000-000002: macrocontext=
Line-000000-000003: exten=5003
Line-000000-000004: rdnis=unknown
Line-000000-000005: priority=15
Line-000000-000006: callerchan=SIP/103-0000008e
Line-000000-000007: callerid="Pxxxx Sxxx" <***********>
Line-000000-000008: origdate=Wed Aug  3 12:02:30 PM UTC 2022
Line-000000-000009: origtime=1659528150
Line-000000-000010: category=
Line-000000-000011: msg_id=1659528150-00000010
Line-000000-000012: flag=
Line-000000-000013: duration=21

So it would appear that all I need is folder and file lists, because existing functionality gets me the file lines :slight_smile:

I took this a little further, it appears as long as the target file is the same section/kv format as asterisk, that asterisk can read pretty much any config file on the server!

action:getconfig
filename:/etc/passwd

Response: Error
Message: Config file has invalid format

action:getconfig
filename:/etc/samba/smb.conf

Response: Success
Category-000000: global
Line-000000-000000: workgroup=DIGISOLUTIONS
Line-000000-000001: server string=%h server (Samba, Ubuntu)
Line-000000-000002: log file=/var/log/samba/log.%m
Line-000000-000003: max log size=1000
Line-000000-000004: logging=file
Line-000000-000005: panic action=/usr/share/samba/panic-action %d
Line-000000-000006: server role=standalone server
Line-000000-000007: obey pam restrictions=yes
Line-000000-000008: unix password sync=yes
Line-000000-000009: passwd program=/usr/bin/passwd %u
Line-000000-000010: passwd chat=*Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
Line-000000-000011: pam password change=yes
Line-000000-000012: map to guest=bad user
Line-000000-000013: usershare allow guests=yes
Category-000001: homes
Line-000001-000000: comment=Home Directories
Line-000001-000001: browseable=yes
Line-000001-000002: read only=no
Line-000001-000003: create mask=0600
Line-000001-000004: directory mask=0700
Line-000001-000005: valid users=%S

:astonished::astonished:

I’ve unlisted this topic for now due to that. Go ahead and create a security issue on System Dashboard - Digium/Asterisk JIRA and we’ll decide on whether it’s a security issue or not. As it is it requires AMI access with specific classes, and the user Asterisk is running as has to have permissions to the files. With this level of access as well you could reconfigure Asterisk to be wide open. The permissions allow… a lot of ways for an attacker to do things, aside from what you’ve found.

@jcolp no worries. I can remove the posts if you wish.

I was thinking to myself last night, that a good fix would be to look at the ownership on the files and if not asterisk:asterisk then refuse to read the file.

The ability to actually read the VPS message info files, is actually very useful (As you’ll see from the thread preceding my recent comments), restricting asterisk to only reading /etc/asterisk would prevent this.

That said, I have actually gotten my manager extensions working, but I don’t have time to contribute it as an official addition to the project at the moment as the project stakeholders need me to finish the work on their project.

My extension do have a vps txt file" read routine that only allows VPS file access and nothing else.

I’ll raise it as a security issue as per your request.