/* $NetBSD: umbctl.c,v 1.4 2020/05/13 21:44:30 khorben Exp $ */ /* * Copyright (c) 2018 Pierre Pronchery * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE DEVELOPERS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include /* constants */ static const struct umb_valdescr _umb_regstate[] = MBIM_REGSTATE_DESCRIPTIONS; static const struct umb_valdescr _umb_dataclass[] = MBIM_DATACLASS_DESCRIPTIONS; static const struct umb_valdescr _umb_state[] = UMB_INTERNAL_STATE_DESCRIPTIONS; static const struct umb_valdescr _umb_regmode[] = { { MBIM_REGMODE_UNKNOWN, "unknown" }, { MBIM_REGMODE_AUTOMATIC, "automatic" }, { MBIM_REGMODE_MANUAL, "manual" }, { 0, NULL } }; static const struct umb_valdescr _umb_ber[] = { { UMB_BER_EXCELLENT, "excellent" }, { UMB_BER_VERYGOOD, "very good" }, { UMB_BER_GOOD, "good" }, { UMB_BER_OK, "ok" }, { UMB_BER_MEDIUM, "medium" }, { UMB_BER_BAD, "bad" }, { UMB_BER_VERYBAD, "very bad" }, { UMB_BER_EXTREMELYBAD, "extremely bad" }, { 0, NULL } }; /* prototypes */ static int _char_to_utf16(const char * in, uint16_t * out, size_t outlen); static int _error(int ret, char const * format, ...); static int _umbctl(char const * ifname, int verbose, int argc, char * argv[]); static int _umbctl_file(char const * ifname, char const * filename, int verbose); static void _umbctl_info(char const * ifname, struct umb_info * umbi); static int _umbctl_ioctl(char const * ifname, int fd, unsigned long request, struct ifreq * ifr); static int _umbctl_set(char const * ifname, struct umb_parameter * umbp, int argc, char * argv[]); static int _umbctl_socket(void); static int _usage(void); static void _utf16_to_char(uint16_t *in, int inlen, char *out, size_t outlen); /* functions */ /* char_to_utf16 */ /* this function is from OpenBSD's ifconfig(8) */ static int _char_to_utf16(const char * in, uint16_t * out, size_t outlen) { int n = 0; uint16_t c; for (;;) { c = *in++; if (c == '\0') { /* * NUL termination is not required, but zero out the * residual buffer */ memset(out, 0, outlen); return n; } if (outlen < sizeof(*out)) return -1; *out++ = htole16(c); n += sizeof(*out); outlen -= sizeof(*out); } } /* error */ __printflike(2, 3) static int _error(int ret, char const * format, ...) { va_list ap; fputs("umbctl: ", stderr); va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fputs("\n", stderr); return ret; } /* umbctl */ static int _umbctl(char const * ifname, int verbose, int argc, char * argv[]) { int fd; struct ifreq ifr; struct umb_info umbi; struct umb_parameter umbp; if((fd = _umbctl_socket()) < 0) return 2; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if(argc != 0) { memset(&umbp, 0, sizeof(umbp)); ifr.ifr_data = &umbp; if(_umbctl_ioctl(ifname, fd, SIOCGUMBPARAM, &ifr) != 0 || _umbctl_set(ifname, &umbp, argc, argv) != 0 || _umbctl_ioctl(ifname, fd, SIOCSUMBPARAM, &ifr) != 0) { close(fd); return 2; } } if(argc == 0 || verbose > 0) { ifr.ifr_data = &umbi; if(_umbctl_ioctl(ifname, fd, SIOCGUMBINFO, &ifr) != 0) { close(fd); return 3; } _umbctl_info(ifname, &umbi); } if(close(fd) != 0) return _error(2, "%s: %s", ifname, strerror(errno)); return 0; } /* umbctl_file */ static int _umbctl_file(char const * ifname, char const * filename, int verbose) { int ret = 0; int fd; struct ifreq ifr; struct umb_info umbi; struct umb_parameter umbp; FILE * fp; char buf[512]; size_t len; int i; int eof; char * tokens[3] = { buf, NULL, NULL }; char * p; if((fp = fopen(filename, "r")) == NULL) return _error(2, "%s: %s", filename, strerror(errno)); memset(&umbp, 0, sizeof(umbp)); while(fgets(buf, sizeof(buf), fp) != NULL) { if(buf[0] == '#') continue; buf[sizeof(buf) - 1] = '\0'; if((len = strlen(buf)) > 0) { if(buf[len - 1] != '\n') { ret = _error(2, "%s: %s", filename, "Line too long"); while((i = fgetc(fp)) != EOF && i != '\n'); continue; } else buf[len - 1] = '\0'; } if((p = strchr(buf, '=')) != NULL) { tokens[1] = p + 1; *p = '\0'; } else tokens[1] = NULL; ret |= _umbctl_set(ifname, &umbp, (p != NULL) ? 2 : 1, tokens) ? 2 : 0; } eof = feof(fp); if(fclose(fp) != 0 || !eof) return _error(2, "%s: %s", filename, strerror(errno)); if(ret != 0) return ret; if((fd = _umbctl_socket()) < 0) return 2; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); ifr.ifr_data = &umbp; if(_umbctl_ioctl(ifname, fd, SIOCGUMBPARAM, &ifr) != 0 || _umbctl_ioctl(ifname, fd, SIOCSUMBPARAM, &ifr) != 0) { close(fd); return 2; } if(verbose > 0) { ifr.ifr_data = &umbi; if(_umbctl_ioctl(ifname, fd, SIOCGUMBINFO, &ifr) != 0) { close(fd); return 3; } _umbctl_info(ifname, &umbi); } if(close(fd) != 0) return _error(2, "%s: %s", ifname, strerror(errno)); return 0; } /* umbctl_info */ static void _umbctl_info(char const * ifname, struct umb_info * umbi) { char provider[UMB_PROVIDERNAME_MAXLEN + 1]; char pn[UMB_PHONENR_MAXLEN + 1]; char roaming[UMB_ROAMINGTEXT_MAXLEN + 1]; char apn[UMB_APN_MAXLEN + 1]; char fwinfo[UMB_FWINFO_MAXLEN + 1]; char hwinfo[UMB_HWINFO_MAXLEN + 1]; _utf16_to_char(umbi->provider, UMB_PROVIDERNAME_MAXLEN, provider, sizeof(provider)); _utf16_to_char(umbi->pn, UMB_PHONENR_MAXLEN, pn, sizeof(pn)); _utf16_to_char(umbi->roamingtxt, UMB_ROAMINGTEXT_MAXLEN, roaming, sizeof(roaming)); _utf16_to_char(umbi->apn, UMB_APN_MAXLEN, apn, sizeof(apn)); _utf16_to_char(umbi->fwinfo, UMB_FWINFO_MAXLEN, fwinfo, sizeof(fwinfo)); _utf16_to_char(umbi->hwinfo, UMB_HWINFO_MAXLEN, hwinfo, sizeof(hwinfo)); printf("%s: state %s, mode %s, registration %s\n" "\tprovider \"%s\", dataclass %s, signal %s\n" "\tphone number \"%s\", roaming \"%s\" (%s)\n" "\tAPN \"%s\", TX %" PRIu64 ", RX %" PRIu64 "\n" "\tfirmware \"%s\", hardware \"%s\"\n", ifname, umb_val2descr(_umb_state, umbi->state), umb_val2descr(_umb_regmode, umbi->regmode), umb_val2descr(_umb_regstate, umbi->regstate), provider, umb_val2descr(_umb_dataclass, umbi->cellclass), umb_val2descr(_umb_ber, umbi->ber), pn, roaming, umbi->enable_roaming ? "allowed" : "denied", apn, umbi->uplink_speed, umbi->downlink_speed, fwinfo, hwinfo); } /* umbctl_ioctl */ static int _umbctl_ioctl(char const * ifname, int fd, unsigned long request, struct ifreq * ifr) { if(ioctl(fd, request, ifr) != 0) return _error(-1, "%s: %s", ifname, strerror(errno)); return 0; } /* umbctl_set */ /* callbacks */ static int _set_apn(char const *, struct umb_parameter *, char const *); static int _set_username(char const *, struct umb_parameter *, char const *); static int _set_password(char const *, struct umb_parameter *, char const *); static int _set_pin(char const *, struct umb_parameter *, char const *); static int _set_puk(char const *, struct umb_parameter *, char const *); static int _set_roaming_allow(char const *, struct umb_parameter *, char const *); static int _set_roaming_deny(char const *, struct umb_parameter *, char const *); static int _umbctl_set(char const * ifname, struct umb_parameter * umbp, int argc, char * argv[]) { struct { char const * name; int (*callback)(char const *, struct umb_parameter *, char const *); int parameter; } callbacks[] = { { "apn", _set_apn, 1 }, { "username", _set_username, 1 }, { "password", _set_password, 1 }, { "pin", _set_pin, 1 }, { "puk", _set_puk, 1 }, { "roaming", _set_roaming_allow, 0 }, { "-roaming", _set_roaming_deny, 0 }, }; int i; size_t j; for(i = 0; i < argc; i++) { for(j = 0; j < sizeof(callbacks) / sizeof(*callbacks); j++) if(strcmp(argv[i], callbacks[j].name) == 0) { if(callbacks[j].parameter && i + 1 == argc) return _error(-1, "%s: Incomplete" " parameter", argv[i]); if(callbacks[j].callback(ifname, umbp, callbacks[j].parameter ? argv[i + 1] : NULL)) return -1; if(callbacks[j].parameter) i++; break; } if(j == sizeof(callbacks) / sizeof(*callbacks)) return _error(-1, "%s: Unknown parameter", argv[i]); } return 0; } static int _set_apn(char const * ifname, struct umb_parameter * umbp, char const * apn) { umbp->apnlen = _char_to_utf16(apn, umbp->apn, sizeof(umbp->apn)); if(umbp->apnlen < 0 || (size_t)umbp->apnlen > sizeof(umbp->apn)) return _error(-1, "%s: %s", ifname, "APN too long"); return 0; } static int _set_username(char const * ifname, struct umb_parameter * umbp, char const * username) { umbp->usernamelen = _char_to_utf16(username, umbp->username, sizeof(umbp->username)); if(umbp->usernamelen < 0 || (size_t)umbp->usernamelen > sizeof(umbp->username)) return _error(-1, "%s: %s", ifname, "Username too long"); return 0; } static int _set_password(char const * ifname, struct umb_parameter * umbp, char const * password) { umbp->passwordlen = _char_to_utf16(password, umbp->password, sizeof(umbp->password)); if(umbp->passwordlen < 0 || (size_t)umbp->passwordlen > sizeof(umbp->password)) return _error(-1, "%s: %s", ifname, "Password too long"); return 0; } static int _set_pin(char const * ifname, struct umb_parameter * umbp, char const * pin) { umbp->is_puk = 0; umbp->op = MBIM_PIN_OP_ENTER; umbp->pinlen = _char_to_utf16(pin, umbp->pin, sizeof(umbp->pin)); if(umbp->pinlen < 0 || (size_t)umbp->pinlen > sizeof(umbp->pin)) return _error(-1, "%s: %s", ifname, "PIN code too long"); return 0; } static int _set_puk(char const * ifname, struct umb_parameter * umbp, char const * puk) { umbp->is_puk = 1; umbp->op = MBIM_PIN_OP_ENTER; umbp->pinlen = _char_to_utf16(puk, umbp->pin, sizeof(umbp->pin)); if(umbp->pinlen < 0 || (size_t)umbp->pinlen > sizeof(umbp->pin)) return _error(-1, "%s: %s", ifname, "PUK code too long"); return 0; } static int _set_roaming_allow(char const * ifname, struct umb_parameter * umbp, char const * unused) { (void) ifname; (void) unused; umbp->roaming = 1; return 0; } static int _set_roaming_deny(char const * ifname, struct umb_parameter * umbp, char const * unused) { (void) ifname; (void) unused; umbp->roaming = 0; return 0; } /* umbctl_socket */ static int _umbctl_socket(void) { int fd; if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return _error(-1, "socket: %s", strerror(errno)); return fd; } /* usage */ static int _usage(void) { fputs("Usage: umbctl [-v] ifname [parameter [value]] [...]\n" " umbctl -f config-file ifname [...]\n", stderr); return 1; } /* utf16_to_char */ static void _utf16_to_char(uint16_t *in, int inlen, char *out, size_t outlen) { uint16_t c; while (outlen > 0) { c = inlen > 0 ? htole16(*in) : 0; if (c == 0 || --outlen == 0) { /* always NUL terminate result */ *out = '\0'; break; } *out++ = isascii(c) ? (char)c : '?'; in++; inlen--; } } /* main */ int main(int argc, char * argv[]) { int o; char const * filename = NULL; int verbose = 0; while((o = getopt(argc, argv, "f:v")) != -1) switch(o) { case 'f': filename = optarg; break; case 'v': verbose++; break; default: return _usage(); } if(optind == argc) return _usage(); if(filename != NULL) { if(optind + 1 != argc) return _usage(); return _umbctl_file(argv[optind], filename, verbose); } return _umbctl(argv[optind], verbose, argc - optind - 1, &argv[optind + 1]); }