1 /* Generic DDNS plugin
2 *
3 * Copyright (C) 2003-2004 Narcis Ilisei <inarcis2002@hotpop.com>
4 * Copyright (C) 2006 Steve Horbachuk
5 * Copyright (C) 2010-2021 Joachim Wiberg <troglobit@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, visit the Free Software Foundation
19 * website at http://www.gnu.org/licenses/gpl-2.0.html or write to the
20 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include <ctype.h>
25 #include "plugin.h"
26
27 /*
28 * Generic update format for sites that perform the update using:
29 *
30 * http://some.address.domain/somesubdir?some_param_name=ALIAS
31 *
32 * With the standard http stuff and basic base64 encoded auth.
33 * The parameter here is the entire request, except the the alias.
34 */
35 #define GENERIC_BASIC_AUTH_UPDATE_IP_REQUEST \
36 "GET %s%s " \
37 "HTTP/1.0\r\n" \
38 "Host: %s\r\n" \
39 "Authorization: Basic %s\r\n" \
40 "User-Agent: %s\r\n\r\n"
41
42 const char * const generic_responses[] =
43 { "OK", "good", "true", "updated", "nochg", NULL };
44
45 static int request (ddns_t *ctx, ddns_info_t *info, ddns_alias_t *alias);
46 static int response (http_trans_t *trans, ddns_info_t *info, ddns_alias_t *alias);
47
48 static ddns_system_t generic = {
49 .name = "custom",
50
51 .request = (req_fn_t)request,
52 .response = (rsp_fn_t)response,
53
54 .checkip_name = DDNS_MY_IP_SERVER,
55 .checkip_url = DDNS_MY_CHECKIP_URL,
56 .checkip_ssl = DDNS_MY_IP_SSL,
57
58 .server_name = "",
59 .server_url = ""
60 };
61
62 /* Replace %? with @str */
replace_fmt(char * fmt,char * str,size_t fmtlen)63 static void replace_fmt(char *fmt, char *str, size_t fmtlen)
64 {
65 char *src = fmt + fmtlen;
66 size_t len = strlen(str);
67
68 memmove(fmt + len, src, strlen(src) + 1);
69 memcpy(fmt, str, strlen(str));
70 }
71
72 /* Skip %? if it has not been specified */
skip_fmt(char * fmt,size_t len)73 static void skip_fmt(char *fmt, size_t len)
74 {
75 char *src = fmt + len;
76
77 memmove(fmt, src, strlen(src) + 1);
78 }
79
80
81 /*
82 * Fully custom server URL, with % format specifiers
83 *
84 * %u - username
85 * %p - password, if HTTP basic auth is not used
86 * %h - hostname
87 * %i - IP address
88 */
custom_server_url(ddns_info_t * info,ddns_alias_t * alias)89 static int custom_server_url(ddns_info_t *info, ddns_alias_t *alias)
90 {
91 char *ptr;
92
93 while ((ptr = strchr(info->server_url, '%'))) {
94 if (!strncmp(ptr, "%u", 2)) {
95 if (strnlen(info->creds.username, USERNAME_LEN) <= 0) {
96 logit(LOG_ERR, "Format specifier in ddns-path used: '%%u',"
97 " but 'username' configuration option has not been specified!");
98 skip_fmt(ptr, 2);
99 } else {
100 replace_fmt(ptr, info->creds.username, 2);
101 }
102
103 continue;
104 }
105
106 if (!strncmp(ptr, "%p", 2)) {
107 if (strnlen(info->creds.password, PASSWORD_LEN) <= 0) {
108 logit(LOG_ERR, "Format specifier in ddns-path used: '%%p',"
109 " but 'password' configuration option has not been specified!");
110 skip_fmt(ptr, 2);
111 } else {
112 replace_fmt(ptr, info->creds.password, 2);
113 }
114
115 continue;
116 }
117
118 if (!strncmp(ptr, "%h", 2)) {
119 replace_fmt(ptr, alias->name, 2);
120 continue;
121 }
122
123 if (!strncmp(ptr, "%i", 2)) {
124 replace_fmt(ptr, alias->address, 2);
125 continue;
126 }
127
128 /* NOTE: This should be the last one */
129 if (!strncmp(ptr, "%%", 2)) {
130 replace_fmt(ptr, "%", 2);
131 continue;
132 }
133
134 logit(LOG_ERR, "Unknown format specifier in ddns-path: '%c'", ptr[1]);
135 return -1;
136 }
137
138 return strlen(info->server_url);
139 }
140
tohex(char code)141 static char tohex(char code)
142 {
143 static const char hex[] = "0123456789abcdef";
144
145 return hex[code & 15];
146 }
147
148 /* Used to check if user already URL encoded */
ishex(char * str)149 static int ishex(char *str)
150 {
151 if (strlen(str) < 3)
152 return 0;
153
154 if (str[0] == '%' && isxdigit(str[1]) && isxdigit(str[2]))
155 return 1;
156
157 return 0;
158 }
159
160 /*
161 * Simple URL encoder, with exceptions for /, ?, =, and &, which should
162 * usually be encoded as well, but are here exposed raw to advanced
163 * end-users.
164 */
url_encode(char * str)165 static char *url_encode(char *str)
166 {
167 char *buf, *ptr;
168
169 buf = calloc(strlen(str) * 3 + 1, sizeof(char));
170 if (!buf)
171 return NULL;
172 ptr = buf;
173
174 while (str[0]) {
175 char ch = str[0];
176
177 if (ishex(str)) {
178 *ptr++ = *str++;
179 *ptr++ = *str++;
180 *ptr++ = *str++;
181 continue;
182 }
183
184 if (isalnum(ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~')
185 *ptr++ = ch;
186 else if (ch == '/' || ch == '?' || ch == '&' || ch == '=')
187 *ptr++ = ch;
188 else if (ch == ' ')
189 *ptr++ = '+';
190 else
191 *ptr++ = '%', *ptr++ = tohex(ch >> 4), *ptr++ = tohex(ch & 15);
192 str++;
193 }
194 *ptr = '\0';
195
196 return buf;
197 }
198
199 /*
200 * This function is called for every listed hostname alias in the
201 * custom{} section. There is currently no way to only call it
202 * once, in case a DDNS provider supports many hostnames in the
203 * HTTP GET URL.
204 */
request(ddns_t * ctx,ddns_info_t * info,ddns_alias_t * alias)205 static int request(ddns_t *ctx, ddns_info_t *info, ddns_alias_t *alias)
206 {
207 int ret;
208 char *url;
209 char *arg = "";
210
211 /*
212 * if the user has specified modifiers, then he is probably
213 * aware of how to append his hostname or IP, otherwise just
214 * append the hostname or ip (depending on the append_myip option)
215 */
216 if (strchr(info->server_url, '%')) {
217 if (custom_server_url(info, alias) <= 0)
218 logit(LOG_ERR, "Invalid server URL: %s", info->server_url);
219 } else {
220 /* Backwards compat, default to append hostname */
221 arg = alias->name;
222
223 if (info->append_myip)
224 arg = alias->address;
225 }
226
227 url = url_encode(info->server_url);
228 if (!url)
229 return 0;
230
231 ret = snprintf(ctx->request_buf, ctx->request_buflen,
232 GENERIC_BASIC_AUTH_UPDATE_IP_REQUEST,
233 url, arg,
234 info->server_name.name,
235 info->creds.encoded_password,
236 info->user_agent);
237 free(url);
238
239 return ret;
240 }
241
response(http_trans_t * trans,ddns_info_t * info,ddns_alias_t * alias)242 static int response(http_trans_t *trans, ddns_info_t *info, ddns_alias_t *alias)
243 {
244 char *resp = trans->rsp_body;
245 size_t i;
246
247 (void)info;
248 (void)alias;
249
250 DO(http_status_valid(trans->status));
251
252 for (i = 0; i < info->server_response_num; i++) {
253 if (strcasestr(resp, info->server_response[i]))
254 return 0;
255 }
256
257 return RC_DDNS_RSP_NOTOK;
258 }
259
PLUGIN_INIT(plugin_init)260 PLUGIN_INIT(plugin_init)
261 {
262 plugin_register(&generic);
263 }
264
PLUGIN_EXIT(plugin_exit)265 PLUGIN_EXIT(plugin_exit)
266 {
267 plugin_unregister(&generic);
268 }
269
270 /**
271 * Local Variables:
272 * indent-tabs-mode: t
273 * c-file-style: "linux"
274 * End:
275 */
276