diff options
| -rw-r--r-- | include/applets.h | 3 | ||||
| -rw-r--r-- | include/usage.h | 7 | ||||
| -rw-r--r-- | networking/Config.in | 7 | ||||
| -rw-r--r-- | networking/Makefile.in | 1 | ||||
| -rw-r--r-- | networking/fakeidentd.c | 441 |
5 files changed, 459 insertions, 0 deletions
diff --git a/include/applets.h b/include/applets.h index 90a081550..7771c17a9 100644 --- a/include/applets.h +++ b/include/applets.h | |||
| @@ -185,6 +185,9 @@ | |||
| 185 | #ifdef CONFIG_EXPR | 185 | #ifdef CONFIG_EXPR |
| 186 | APPLET(expr, expr_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER) | 186 | APPLET(expr, expr_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER) |
| 187 | #endif | 187 | #endif |
| 188 | #ifdef CONFIG_FAKEIDENTD | ||
| 189 | APPLET(fakeidentd, fakeidentd_main, _BB_DIR_USR_SBIN, _BB_SUID_NEVER) | ||
| 190 | #endif | ||
| 188 | #ifdef CONFIG_FALSE | 191 | #ifdef CONFIG_FALSE |
| 189 | APPLET(false, false_main, _BB_DIR_BIN, _BB_SUID_NEVER) | 192 | APPLET(false, false_main, _BB_DIR_BIN, _BB_SUID_NEVER) |
| 190 | #endif | 193 | #endif |
diff --git a/include/usage.h b/include/usage.h index 6d594dc01..128aee2f5 100644 --- a/include/usage.h +++ b/include/usage.h | |||
| @@ -575,6 +575,13 @@ | |||
| 575 | "\\( and \\) or null; if \\( and \\) are not used, they return the number \n" \ | 575 | "\\( and \\) or null; if \\( and \\) are not used, they return the number \n" \ |
| 576 | "of characters matched or 0." | 576 | "of characters matched or 0." |
| 577 | 577 | ||
| 578 | #define fakeidentd_trivial_usage \ | ||
| 579 | "[-b ip] [STRING]" | ||
| 580 | #define fakeidentd_full_usage \ | ||
| 581 | "Returns a set string to auth requests\n\n"\ | ||
| 582 | "\t-b\tBind to ip address\n"\ | ||
| 583 | "\tSTRING\tThe ident answer string (default is nobody)" | ||
| 584 | |||
| 578 | #define false_trivial_usage \ | 585 | #define false_trivial_usage \ |
| 579 | "" | 586 | "" |
| 580 | #define false_full_usage \ | 587 | #define false_full_usage \ |
diff --git a/networking/Config.in b/networking/Config.in index 42176f050..cf2f1dcc1 100644 --- a/networking/Config.in +++ b/networking/Config.in | |||
| @@ -18,6 +18,13 @@ config CONFIG_ARPING | |||
| 18 | help | 18 | help |
| 19 | Ping hosts by ARP packets | 19 | Ping hosts by ARP packets |
| 20 | 20 | ||
| 21 | config CONFIG_FAKEIDENTD | ||
| 22 | bool "fakeidentd" | ||
| 23 | default n | ||
| 24 | help | ||
| 25 | fakeidentd listens to the ident port and returns a set fake | ||
| 26 | value whatever it gets. | ||
| 27 | |||
| 21 | config CONFIG_FTPGET | 28 | config CONFIG_FTPGET |
| 22 | bool "ftpget" | 29 | bool "ftpget" |
| 23 | default n | 30 | default n |
diff --git a/networking/Makefile.in b/networking/Makefile.in index 9bfe90176..4e27dbc67 100644 --- a/networking/Makefile.in +++ b/networking/Makefile.in | |||
| @@ -24,6 +24,7 @@ endif | |||
| 24 | srcdir=$(top_srcdir)/networking | 24 | srcdir=$(top_srcdir)/networking |
| 25 | NETWORKING-y:= | 25 | NETWORKING-y:= |
| 26 | NETWORKING-$(CONFIG_ARPING) += arping.o | 26 | NETWORKING-$(CONFIG_ARPING) += arping.o |
| 27 | NETWORKING-$(CONFIG_FAKEIDENTD) += fakeidentd.o | ||
| 27 | NETWORKING-$(CONFIG_FTPGET) += ftpgetput.o | 28 | NETWORKING-$(CONFIG_FTPGET) += ftpgetput.o |
| 28 | NETWORKING-$(CONFIG_FTPPUT) += ftpgetput.o | 29 | NETWORKING-$(CONFIG_FTPPUT) += ftpgetput.o |
| 29 | NETWORKING-$(CONFIG_HOSTNAME) += hostname.o | 30 | NETWORKING-$(CONFIG_HOSTNAME) += hostname.o |
diff --git a/networking/fakeidentd.c b/networking/fakeidentd.c new file mode 100644 index 000000000..560f6bab0 --- /dev/null +++ b/networking/fakeidentd.c | |||
| @@ -0,0 +1,441 @@ | |||
| 1 | /* vi: set sw=4 ts=4: */ | ||
| 2 | /* | ||
| 3 | * A fake identd server | ||
| 4 | * | ||
| 5 | * Adapted to busybox by Thomas Lundquist <thomasez@zelow.no> | ||
| 6 | * Original Author: Tomi Ollila <too@iki.fi> | ||
| 7 | * http://www.guru-group.fi/~too/sw/ | ||
| 8 | * | ||
| 9 | * This program is free software; you can redistribute it and/or modify | ||
| 10 | * it under the terms of the GNU General Public License as published by | ||
| 11 | * the Free Software Foundation; either version 2 of the License, or | ||
| 12 | * (at your option) any later version. | ||
| 13 | * | ||
| 14 | * This program is distributed in the hope that it will be useful, | ||
| 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 17 | * General Public License for more details. | ||
| 18 | * | ||
| 19 | * You should have received a copy of the GNU General Public License | ||
| 20 | * along with this program; if not, write to the Free Software | ||
| 21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 22 | * | ||
| 23 | */ | ||
| 24 | |||
| 25 | #include <unistd.h> | ||
| 26 | #include <stdio.h> | ||
| 27 | #include <stdlib.h> | ||
| 28 | #include <stdarg.h> | ||
| 29 | #include <string.h> | ||
| 30 | #include <fcntl.h> | ||
| 31 | #include <signal.h> | ||
| 32 | #include <sys/syslog.h> | ||
| 33 | |||
| 34 | #include <pwd.h> | ||
| 35 | #include <netdb.h> | ||
| 36 | |||
| 37 | #include <sys/syslog.h> | ||
| 38 | #include <sys/types.h> | ||
| 39 | #include <sys/time.h> | ||
| 40 | #include <time.h> | ||
| 41 | #include <sys/socket.h> | ||
| 42 | #include <netinet/in.h> | ||
| 43 | #include <errno.h> | ||
| 44 | #include <arpa/inet.h> | ||
| 45 | #include <sys/uio.h> | ||
| 46 | |||
| 47 | #include "busybox.h" | ||
| 48 | |||
| 49 | #define IDENT_PORT 113 | ||
| 50 | #define MAXCONNS 20 | ||
| 51 | #define MAXIDLETIME 45 | ||
| 52 | |||
| 53 | static const char ident_substr[] = " : USERID : UNIX : "; | ||
| 54 | static const int ident_substr_len = sizeof(ident_substr) - 1; | ||
| 55 | #define PIDFILE "/var/run/identd.pid" | ||
| 56 | |||
| 57 | /* | ||
| 58 | * We have to track the 'first connection socket' so that we | ||
| 59 | * don't go around closing file descriptors for non-clients. | ||
| 60 | * | ||
| 61 | * descriptor setup normally | ||
| 62 | * 0 = server socket | ||
| 63 | * 1 = syslog fd (hopefully -- otherwise this won't work) | ||
| 64 | * 2 = connection socket after detached from tty. standard error before that | ||
| 65 | * 3 - 2 + MAXCONNS = rest connection sockets | ||
| 66 | * | ||
| 67 | * To try to make sure that syslog fd is what is "requested", the that fd | ||
| 68 | * is closed before openlog() call. It can only severely fail if fd 0 | ||
| 69 | * is initially closed. | ||
| 70 | */ | ||
| 71 | #define FCS 2 | ||
| 72 | |||
| 73 | /* | ||
| 74 | * FD of the connection is always the index of the connection structure | ||
| 75 | * in `conns' array + FCS | ||
| 76 | */ | ||
| 77 | struct { | ||
| 78 | char buf[20]; | ||
| 79 | int len; | ||
| 80 | time_t lasttime; | ||
| 81 | } conns[MAXCONNS]; | ||
| 82 | |||
| 83 | /* When using global variables, bind those at least to a structure. */ | ||
| 84 | struct { | ||
| 85 | const char *identuser; | ||
| 86 | fd_set readfds; | ||
| 87 | int conncnt; | ||
| 88 | } G; | ||
| 89 | |||
| 90 | /* | ||
| 91 | * Prototypes | ||
| 92 | */ | ||
| 93 | static void reply(int s, char *buf); | ||
| 94 | static void replyError(int s, char *buf); | ||
| 95 | |||
| 96 | static const char *nobodystr = "nobody"; /* this needs to be declared like this */ | ||
| 97 | static char *bind_ip_address = "0.0.0.0"; | ||
| 98 | |||
| 99 | static inline void movefd(int from, int to) | ||
| 100 | { | ||
| 101 | if (from != to) { | ||
| 102 | dup2(from, to); | ||
| 103 | close(from); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | static void inetbind(void) | ||
| 108 | { | ||
| 109 | int s, port; | ||
| 110 | struct sockaddr_in addr; | ||
| 111 | int len = sizeof(addr); | ||
| 112 | int one = 1; | ||
| 113 | struct servent *se; | ||
| 114 | |||
| 115 | if ((se = getservbyname("identd", "tcp")) == NULL) | ||
| 116 | port = IDENT_PORT; | ||
| 117 | else | ||
| 118 | port = se->s_port; | ||
| 119 | |||
| 120 | if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) | ||
| 121 | bb_perror_msg_and_die("Cannot create server socket"); | ||
| 122 | |||
| 123 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | ||
| 124 | |||
| 125 | memset(&addr, 0, sizeof(addr)); | ||
| 126 | addr.sin_addr.s_addr = inet_addr(bind_ip_address); | ||
| 127 | addr.sin_family = AF_INET; | ||
| 128 | addr.sin_port = htons(port); | ||
| 129 | |||
| 130 | if (bind(s, (struct sockaddr *)&addr, len) < 0) | ||
| 131 | bb_perror_msg_and_die("Cannot bind() port %i", IDENT_PORT); | ||
| 132 | |||
| 133 | if (listen(s, 5) < 0) | ||
| 134 | bb_perror_msg_and_die("Cannot listen() on port %i", IDENT_PORT); | ||
| 135 | |||
| 136 | movefd(s, 0); | ||
| 137 | } | ||
| 138 | |||
| 139 | static void delpidfile(void) | ||
| 140 | { | ||
| 141 | /* | ||
| 142 | * Usually nobody has no write/delete access to directory /var/run/ | ||
| 143 | * therefore if file cannot be deleted, it is truncated | ||
| 144 | */ | ||
| 145 | if (unlink(PIDFILE) < 0) | ||
| 146 | close(open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0644)); | ||
| 147 | } | ||
| 148 | |||
| 149 | static void handlexitsigs(int signum) | ||
| 150 | { | ||
| 151 | delpidfile(); | ||
| 152 | exit(0); | ||
| 153 | } | ||
| 154 | |||
| 155 | /* May succeed. If not, won't care. */ | ||
| 156 | static void writepid(uid_t nobody, uid_t nogrp) | ||
| 157 | { | ||
| 158 | char buf[24]; | ||
| 159 | int fd = open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0664); | ||
| 160 | |||
| 161 | if (fd < 0) | ||
| 162 | return; | ||
| 163 | |||
| 164 | snprintf(buf, 23, "%d\n", getpid()); | ||
| 165 | write(fd, buf, strlen(buf)); | ||
| 166 | fchown(fd, nobody, nogrp); | ||
| 167 | close(fd); | ||
| 168 | |||
| 169 | /* should this handle ILL, ... (see signal(7)) */ | ||
| 170 | signal(SIGTERM, handlexitsigs); | ||
| 171 | signal(SIGINT, handlexitsigs); | ||
| 172 | signal(SIGQUIT, handlexitsigs); | ||
| 173 | } | ||
| 174 | |||
| 175 | /* return 0 as parent, 1 as child */ | ||
| 176 | static int godaemon(void) | ||
| 177 | { | ||
| 178 | uid_t nobody, nogrp; | ||
| 179 | struct passwd *pw; | ||
| 180 | |||
| 181 | switch (fork()) { | ||
| 182 | case -1: | ||
| 183 | bb_perror_msg_and_die("Could not fork"); | ||
| 184 | |||
| 185 | case 0: | ||
| 186 | pw = getpwnam(nobodystr); | ||
| 187 | if (pw == NULL) | ||
| 188 | bb_error_msg_and_die("Cannot find uid/gid of user '%s'", nobodystr); | ||
| 189 | nobody = pw->pw_uid; | ||
| 190 | nogrp = pw->pw_gid; | ||
| 191 | writepid(nobody, nogrp); | ||
| 192 | |||
| 193 | close(0); | ||
| 194 | inetbind(); | ||
| 195 | if (setgid(nogrp)) bb_error_msg_and_die("Could not setgid()"); | ||
| 196 | if (setegid(nogrp)) bb_error_msg_and_die("Could not setegid()"); | ||
| 197 | if (setuid(nobody)) bb_error_msg_and_die("Could not setuid()"); | ||
| 198 | if (seteuid(nobody)) bb_error_msg_and_die("Could not seteuid()"); | ||
| 199 | close(1); | ||
| 200 | close(2); | ||
| 201 | |||
| 202 | signal(SIGHUP, SIG_IGN); | ||
| 203 | signal(SIGPIPE, SIG_IGN); /* connection closed when writing (raises ???) */ | ||
| 204 | |||
| 205 | setsid(); | ||
| 206 | |||
| 207 | openlog(bb_applet_name, 0, LOG_DAEMON); | ||
| 208 | return 1; | ||
| 209 | } | ||
| 210 | |||
| 211 | return 0; | ||
| 212 | } | ||
| 213 | |||
| 214 | static void deleteConn(int s) | ||
| 215 | { | ||
| 216 | int i = s - FCS; | ||
| 217 | |||
| 218 | close(s); | ||
| 219 | |||
| 220 | G.conncnt--; | ||
| 221 | |||
| 222 | /* | ||
| 223 | * Most of the time there is 0 connections. Most often that there | ||
| 224 | * is connections, there is just one connection. When this one connection | ||
| 225 | * closes, i == G.conncnt = 0 -> no copying. | ||
| 226 | * When there is more than one connection, the oldest connections closes | ||
| 227 | * earlier on average. When this happens, the code below starts copying | ||
| 228 | * the connection structure w/ highest index to the place which which is | ||
| 229 | * just deleted. This means that the connection structures are no longer | ||
| 230 | * in chronological order. I'd quess this means that when there is more | ||
| 231 | * than 1 connection, on average every other connection structure needs | ||
| 232 | * to be copied over the time all these connections are deleted. | ||
| 233 | */ | ||
| 234 | if (i != G.conncnt) { | ||
| 235 | memcpy(&conns[i], &conns[G.conncnt], sizeof(conns[0])); | ||
| 236 | movefd(G.conncnt + FCS, s); | ||
| 237 | } | ||
| 238 | |||
| 239 | FD_CLR(G.conncnt + FCS, &G.readfds); | ||
| 240 | } | ||
| 241 | |||
| 242 | static int closeOldest(void) | ||
| 243 | { | ||
| 244 | time_t min = conns[0].lasttime; | ||
| 245 | int idx = 0; | ||
| 246 | int i; | ||
| 247 | |||
| 248 | for (i = 1; i < MAXCONNS; i++) | ||
| 249 | if (conns[i].lasttime < min) | ||
| 250 | idx = i; | ||
| 251 | |||
| 252 | replyError(idx + FCS, "X-SERVER-TOO-BUSY"); | ||
| 253 | close(idx + FCS); | ||
| 254 | |||
| 255 | return idx; | ||
| 256 | } | ||
| 257 | |||
| 258 | static int checkInput(char *buf, int len, int l) | ||
| 259 | { | ||
| 260 | int i; | ||
| 261 | for (i = len; i < len + l; ++i) | ||
| 262 | if (buf[i] == '\n') | ||
| 263 | return 1; | ||
| 264 | return 0; | ||
| 265 | } | ||
| 266 | |||
| 267 | int fakeidentd_main(int argc, char **argv) | ||
| 268 | { | ||
| 269 | int flag; | ||
| 270 | |||
| 271 | memset(conns, 0, sizeof(conns)); | ||
| 272 | memset(&G, 0, sizeof(G)); | ||
| 273 | FD_ZERO(&G.readfds); | ||
| 274 | FD_SET(0, &G.readfds); | ||
| 275 | |||
| 276 | /* handle -b <ip> parameter */ | ||
| 277 | while ((flag = getopt(argc, argv, "b:")) != EOF) { | ||
| 278 | switch (flag) { | ||
| 279 | case 'b': | ||
| 280 | bind_ip_address = optarg; | ||
| 281 | break; | ||
| 282 | default: | ||
| 283 | bb_show_usage(); | ||
| 284 | } | ||
| 285 | } | ||
| 286 | /* handle optional REPLY STRING */ | ||
| 287 | if (optind < argc) | ||
| 288 | G.identuser = argv[optind]; | ||
| 289 | else | ||
| 290 | G.identuser = nobodystr; | ||
| 291 | |||
| 292 | /* daemonize and have the parent return */ | ||
| 293 | if (godaemon() == 0) | ||
| 294 | return 0; | ||
| 295 | |||
| 296 | while (1) { | ||
| 297 | fd_set rfds = G.readfds; | ||
| 298 | struct timeval tv = { 15, 0 }; | ||
| 299 | int i; | ||
| 300 | int tim = time(NULL); | ||
| 301 | |||
| 302 | select(G.conncnt + FCS, &rfds, NULL, NULL, G.conncnt? &tv: NULL); | ||
| 303 | |||
| 304 | for (i = G.conncnt - 1; i >= 0; i--) { | ||
| 305 | int s = i + FCS; | ||
| 306 | |||
| 307 | if (FD_ISSET(s, &rfds)) { | ||
| 308 | char *buf = conns[i].buf; | ||
| 309 | unsigned int len = conns[i].len; | ||
| 310 | unsigned int l; | ||
| 311 | |||
| 312 | if ((l = read(s, buf + len, sizeof(conns[0].buf) - len)) > 0) { | ||
| 313 | if (checkInput(buf, len, l)) { | ||
| 314 | reply(s, buf); | ||
| 315 | goto deleteconn; | ||
| 316 | } else if (len + l >= sizeof(conns[0].buf)) { | ||
| 317 | replyError(s, "X-INVALID-REQUEST"); | ||
| 318 | goto deleteconn; | ||
| 319 | } else { | ||
| 320 | conns[i].len += l; | ||
| 321 | } | ||
| 322 | } else { | ||
| 323 | goto deleteconn; | ||
| 324 | } | ||
| 325 | |||
| 326 | conns[i].lasttime = tim; | ||
| 327 | continue; | ||
| 328 | |||
| 329 | deleteconn: | ||
| 330 | deleteConn(s); | ||
| 331 | } else { | ||
| 332 | /* implement as time_after() in linux kernel sources ... */ | ||
| 333 | if (conns[i].lasttime + MAXIDLETIME <= tim) { | ||
| 334 | replyError(s, "X-TIMEOUT"); | ||
| 335 | deleteConn(s); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 340 | if (FD_ISSET(0, &rfds)) { | ||
| 341 | int s = accept(0, NULL, 0); | ||
| 342 | |||
| 343 | if (s < 0) { | ||
| 344 | if (errno != EINTR) /* EINTR */ | ||
| 345 | syslog(LOG_ERR, "accept: %s", strerror(errno)); | ||
| 346 | } else { | ||
| 347 | if (G.conncnt == MAXCONNS) | ||
| 348 | i = closeOldest(); | ||
| 349 | else | ||
| 350 | i = G.conncnt++; | ||
| 351 | |||
| 352 | movefd(s, i + FCS); /* move if not already there */ | ||
| 353 | FD_SET(i + FCS, &G.readfds); | ||
| 354 | |||
| 355 | conns[i].len = 0; | ||
| 356 | conns[i].lasttime = time(NULL); | ||
| 357 | } | ||
| 358 | } | ||
| 359 | } /* end of while(1) */ | ||
| 360 | |||
| 361 | return 0; | ||
| 362 | } | ||
| 363 | |||
| 364 | static int parseAddrs(char *ptr, char **myaddr, char **heraddr); | ||
| 365 | static void reply(int s, char *buf) | ||
| 366 | { | ||
| 367 | char *myaddr, *heraddr; | ||
| 368 | |||
| 369 | myaddr = heraddr = NULL; | ||
| 370 | |||
| 371 | if (parseAddrs(buf, &myaddr, &heraddr)) | ||
| 372 | replyError(s, "X-INVALID-REQUEST"); | ||
| 373 | else { | ||
| 374 | struct iovec iv[6]; | ||
| 375 | iv[0].iov_base = myaddr; iv[0].iov_len = strlen(myaddr); | ||
| 376 | iv[1].iov_base = ", "; iv[1].iov_len = 2; | ||
| 377 | iv[2].iov_base = heraddr; iv[2].iov_len = strlen(heraddr); | ||
| 378 | iv[3].iov_base = (void *)ident_substr; iv[3].iov_len = ident_substr_len; | ||
| 379 | iv[4].iov_base = (void *)G.identuser; iv[4].iov_len = strlen(G.identuser); | ||
| 380 | iv[5].iov_base = "\r\n"; iv[5].iov_len = 2; | ||
| 381 | writev(s, iv, 6); | ||
| 382 | } | ||
| 383 | } | ||
| 384 | |||
| 385 | static void replyError(int s, char *buf) | ||
| 386 | { | ||
| 387 | struct iovec iv[3]; | ||
| 388 | iv[0].iov_base = "0, 0 : ERROR : "; iv[0].iov_len = 15; | ||
| 389 | iv[1].iov_base = buf; iv[1].iov_len = strlen(buf); | ||
| 390 | iv[2].iov_base = "\r\n"; iv[2].iov_len = 2; | ||
| 391 | writev(s, iv, 3); | ||
| 392 | } | ||
| 393 | |||
| 394 | static int chmatch(char c, char *chars) | ||
| 395 | { | ||
| 396 | for (; *chars; chars++) | ||
| 397 | if (c == *chars) | ||
| 398 | return 1; | ||
| 399 | return 0; | ||
| 400 | } | ||
| 401 | |||
| 402 | static int skipchars(char **p, char *chars) | ||
| 403 | { | ||
| 404 | while (chmatch(**p, chars)) | ||
| 405 | (*p)++; | ||
| 406 | if (**p == '\r' || **p == '\n') | ||
| 407 | return 0; | ||
| 408 | return 1; | ||
| 409 | } | ||
| 410 | |||
| 411 | static int parseAddrs(char *ptr, char **myaddr, char **heraddr) | ||
| 412 | { | ||
| 413 | /* parse <port-on-server> , <port-on-client> */ | ||
| 414 | |||
| 415 | if (!skipchars(&ptr, " \t")) | ||
| 416 | return -1; | ||
| 417 | |||
| 418 | *myaddr = ptr; | ||
| 419 | |||
| 420 | if (!skipchars(&ptr, "1234567890")) | ||
| 421 | return -1; | ||
| 422 | |||
| 423 | if (!chmatch(*ptr, " \t,")) | ||
| 424 | return -1; | ||
| 425 | |||
| 426 | *ptr++ = '\0'; | ||
| 427 | |||
| 428 | if (!skipchars(&ptr, " \t,") ) | ||
| 429 | return -1; | ||
| 430 | |||
| 431 | *heraddr = ptr; | ||
| 432 | |||
| 433 | skipchars(&ptr, "1234567890"); | ||
| 434 | |||
| 435 | if (!chmatch(*ptr, " \n\r")) | ||
| 436 | return -1; | ||
| 437 | |||
| 438 | *ptr = '\0'; | ||
| 439 | |||
| 440 | return 0; | ||
| 441 | } | ||
