-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
amicli.c
219 lines (196 loc) · 5.41 KB
/
amicli.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
* CAMI -- C Asterisk Manager Interface "Simple AMI" demo
*
* Copyright (C) 2022, Naveen Albert
*
* Naveen Albert <asterisk@phreaknet.org>
*
* This program is free software, distributed under the terms of
* the Mozilla Public License Version 2.
*/
/*! \file
*
* \brief C Asterisk Manager Interface "Simple CLI"
*
* \author Naveen Albert <asterisk@phreaknet.org>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <cami/cami.h>
/*
* This is a simple program that will use C AMI to log in,
* and then accepts AMI commands on STDIN
* and outputs responses to STDOUT.
* This is useful for debugging AMI commands and responses.
* Raw AMI commands are accepted, but do not include ActionID.
*/
/*! \brief Callback function executing asynchronously when new events are available */
static void ami_callback(struct ami_session *ami, struct ami_event *event)
{
const char *eventname = ami_keyvalue(event, "Event");
(void) ami;
printf("(Callback) Event Received: %s\n", eventname);
#ifdef PRINT_EVENTS
ami_dump_event(event); /* Do something with event */
#endif
ami_event_free(event); /* Free event when done with it */
}
static void ami_disconnect_callback(struct ami_session *ami)
{
(void) ami;
printf("(Callback) AMI was forcibly disconnected...\n");
exit(EXIT_FAILURE);
}
static int single_ami_command(struct ami_session *ami)
{
char action[64];
char buf[8192];
char *pos;
size_t left;
ssize_t res;
struct ami_response *resp;
/* Read a full AMI command,
* then send it all at once with proper CR LF delimiters. */
/* Read Action name */
pos = action;
left = sizeof(action);
res = getline(&pos, &left, stdin);
if (res <= 0) {
return res;
}
/* Remove newline from action */
pos = strchr(pos, '\n');
if (!pos) {
return -1;
}
*pos = '\0';
/* Get rid of Action: */
pos = action;
pos = strchr(pos, ':');
if (!pos) {
fprintf(stderr, "Must begin with 'Action:'\n");
return -1;
}
pos++;
while (isspace(*pos)) {
pos++;
}
memmove(action, pos, strlen(pos) + 1);
/* Read remainder of command */
pos = buf;
left = sizeof(buf);
for (;;) {
res = getline(&pos, &left, stdin);
if (res <= 0) {
return res;
}
if (!strncasecmp(pos, "ActionID:", strlen("ActionID:"))) {
continue; /* Ignore ActionID, since CAMI will autoadd it */
}
pos += res;
left -= res;
if (*(pos - 1) == '\n' && res > 1 && *(pos - 2) != '\r') {
/* Change LF endings into CR LF endings */
*(pos - 1) = '\r';
*pos = '\n';
if (!--left) {
fprintf(stderr, "Buffer exhaustion\n");
return -1; /* No room for NUL */
}
pos++;
*pos = '\0';
}
if (res <= 2) {
break; /* Empty line signals end of action */
}
}
/* Remove final CR LF, since CAMI will add that. */
pos = strrchr(buf, '\r');
if (pos) {
*pos = '\0';
}
resp = ami_action(ami, action, "%s", buf);
if (!resp) {
fprintf(stderr, "AMI action '%s' failed\n", action);
return -1;
}
ami_dump_response(resp);
ami_resp_free(resp); /* Free response when done with it (just LF or CR LF) */
return 1;
}
int main(int argc,char *argv[])
{
char c;
static const char *getopt_settings = "?dhl:p:u:";
char ami_host[92] = "127.0.0.1"; /* Default to localhost */
char ami_username[64] = "";
char ami_password[64] = "";
int debug = 0;
struct ami_session *ami;
while ((c = getopt(argc, argv, getopt_settings)) != -1) {
switch (c) {
case '?':
case 'd':
debug++;
break;
case 'h':
fprintf(stderr, "amicli [options]\n");
fprintf(stderr, " -l hostname (default: 127.0.0.1)\n");
fprintf(stderr, " -p password (default: read from /etc/asterisk/manager.conf if loopback)\n");
fprintf(stderr, " -u username\n");
return 0;
case 'l':
strncpy(ami_host, optarg, sizeof(ami_host) - 1);
ami_host[sizeof(ami_host) - 1] = '\0';
break;
case 'p':
strncpy(ami_password, optarg, sizeof(ami_password) - 1);
ami_password[sizeof(ami_password) - 1] = '\0';
break;
case 'u':
strncpy(ami_username, optarg, sizeof(ami_username) - 1);
ami_username[sizeof(ami_username) - 1] = '\0';
break;
default:
fprintf(stderr, "Invalid option: %c\n", c);
return -1;
}
}
if (debug > 10) {
debug = 10;
}
if (ami_username[0] && !ami_password[0] && !strcmp(ami_host, "127.0.0.1")) {
/* If we're running as a privileged user with access to manager.conf, grab the password ourselves, which is more
* secure than getting as a command line arg from the user (and kind of convenient)
* Not that running as a user with access to the Asterisk config is great either, but, hey...
*/
if (ami_auto_detect_ami_pass(ami_username, ami_password, sizeof(ami_password))) {
fprintf(stderr, "No password specified, and failed to autodetect from /etc/asterisk/manager.conf\n");
return -1;
}
}
if (!ami_username[0]) {
fprintf(stderr, "No username provided (use -u flag)\n");
return -1;
}
ami = ami_connect(ami_host, 0, ami_callback, ami_disconnect_callback);
if (!ami) {
fprintf(stderr, "Failed to connect to %s\n", ami_host);
return -1;
}
ami_set_debug_level(ami, debug);
ami_set_debug(ami, STDERR_FILENO);
ami_set_discard_on_failure(ami, 0);
if (ami_action_login(ami, ami_username, ami_password)) {
fprintf(stderr, "Failed to log in with username %s\n", ami_username);
return -1;
}
fprintf(stderr, "*** Successfully logged in to AMI on %s (%s) ***\n", ami_host, ami_username);
while (single_ami_command(ami) > 0);
ami_disconnect(ami);
ami_destroy(ami);
return 0;
}