From f72fe0844eba2d421fa62c0ebf4dc7aa6131c243 Mon Sep 17 00:00:00 2001 From: zacheiss Date: Mon, 28 Dec 2009 21:23:04 +0000 Subject: [PATCH] Command line printer manipulation client, and build goo. --- clients/Makefile.in | 2 +- clients/eunice/Makefile.in | 50 +++ clients/eunice/eunice.c | 669 +++++++++++++++++++++++++++++++++++++ configure | 3 +- configure.in | 11 +- 5 files changed, 728 insertions(+), 7 deletions(-) create mode 100644 clients/eunice/Makefile.in create mode 100644 clients/eunice/eunice.c diff --git a/clients/Makefile.in b/clients/Makefile.in index 371377c0..d0df3314 100644 --- a/clients/Makefile.in +++ b/clients/Makefile.in @@ -7,7 +7,7 @@ srcdir=@srcdir@ VPATH=@srcdir@ CURSES_SUBDIRS=mailmaint -SUBDIRS=lib addusr blanche chfn chpobox chsh mitch moira mrcheck mrtest stanley stella @CURSES_SUBDIRS@ +SUBDIRS=lib addusr blanche chfn chpobox chsh eunice mitch moira mrcheck mrtest stanley stella @CURSES_SUBDIRS@ all: @for d in $(SUBDIRS); do (echo "### Making $@ in clients/$$d"; cd $$d; $(MAKE) $@) || exit 1; done diff --git a/clients/eunice/Makefile.in b/clients/eunice/Makefile.in new file mode 100644 index 00000000..6f3cda8d --- /dev/null +++ b/clients/eunice/Makefile.in @@ -0,0 +1,50 @@ +# $Id$ + +SHELL=/bin/sh +@SET_MAKE@ + +CC=@CC@ +CPPFLAGS=@CPPFLAGS@ +CFLAGS=@CFLAGS@ +DEFS=@DEFS@ +ALL_CFLAGS=$(CPPFLAGS) $(CFLAGS) $(DEFS) +LIBTOOL=@LIBTOOL@ +LDFLAGS=@LDFLAGS@ +LIBS=../lib/libmrclient.a @LIBS@ +MR_LIBDEP=@MR_LIBDEP@ +INSTALL=@INSTALL@ +INSTALL_PROGRAM=@INSTALL_PROGRAM@ + +srcdir=@srcdir@ +VPATH=@srcdir@ +SRCTOP=@top_srcdir@ +top_builddir=@top_builddir@ +BUILDTOP=../.. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ + +OBJS=eunice.lo + +TARGET=eunice + +.SUFFIXES: .lo + +.c.lo: + $(LIBTOOL) --mode=compile $(CC) -c $(ALL_CFLAGS) $< + +all: $(TARGET) + +clean: + $(LIBTOOL) --mode=clean rm -f $(OBJS) $(TARGET) + +cleandir distclean: clean + rm -f Makefile + +depend: + +install: all + $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) eunice $(DESTDIR)$(bindir) + +$(TARGET): $(OBJS) ../lib/libmrclient.a $(MR_LIBDEP) + $(LIBTOOL) --mode=link $(CC) -o $@ $(LDFLAGS) $(OBJS) $(LIBS) diff --git a/clients/eunice/eunice.c b/clients/eunice/eunice.c new file mode 100644 index 00000000..7de7d356 --- /dev/null +++ b/clients/eunice/eunice.c @@ -0,0 +1,669 @@ +/* + * Command line oriented Moira print queue tool. + * + * Code based on the blanche command line tool and the moira curses tool. + * Copyright (C) 2009 by the Massachusetts Institute of Technology. + * For copying and distribution information, please see the file + * . + * + * Mark Manley, mmanley@MIT.EDU, 12/09 + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct member { + int type; + char *name, *tag; +}; + +const char *deflogserver = "WSLOGGER.MIT.EDU"; + +/* legacy variables that we need to set regardless */ +const char *quotaserver = "[NONE]"; +const char *pageprice = "10"; + +/* argument parsing macro */ +#define argis(a, b) (!strcmp(*arg + 1, a) || !strcmp(*arg + 1, b)) + +/* flags from command line */ +int info_flag, verbose, noauth; +int create_flag, setmac, delete_flag, rename_flag, ka, banner, update_flag; +char *lpracl, *lpcacl; +char *contact, *newname, *printserver, *type, *hwtype, *mac, *hostname, *queuename; +char *duplexname, *logserver, *location, *realname; + +char *queuename, *whoami, *testqueue; + +void usage(char **argv); +int show_printer_info(char *queuename); +int save_printer_info(int argc, char **argv, void *hint); +int save_hwaddr(int argc, char **argv, void *hint); +void recursive_display_list_members(void); +char *get_username(void); +int wrap_mr_query(char *handle, int argc, char **argv, + int (*callback)(int, char **, void *), void *callarg); +void print_query(char *query_name, int argc, char **argv); +int CountArgs(char **info); + +int main(int argc, char **argv) +{ + int status, success; + char **arg = argv; + char *uargv[2]; + char *pargv[PRN_END]; + char *membervec[4]; + struct member *memberstruct; + char *server = NULL, *p; + int i; + + /* clear all flags & lists */ + i = info_flag = verbose = noauth = 0; + setmac = ka = create_flag = delete_flag = update_flag = 0; + location = lpracl = lpcacl = NULL; + logserver = duplexname = realname = newname = printserver = type = hwtype = mac = hostname = NULL; + + + contact = NULL; + whoami = argv[0]; + + success = 1; + + /* parse args */ + while (++arg - argv < argc) + { + if (**arg == '-') + { + if (argis("i", "info")) + info_flag++; + else if (argis("C", "create")) + create_flag++; + else if (argis("D", "delete")) + delete_flag++; + else if (argis("L", "location")) + { + if (arg - argv < argc - 1) + { + ++arg; + update_flag++; + location = *arg; + } + else + usage(argv); + } + else if (argis("r", "remotename")) + { + if (arg - argv < argc - 1) + { + ++arg; + update_flag++; + realname = *arg; + } + else + usage(argv); + } + else if (argis("T", "type")) + { + if (arg - argv < argc - 1) + { + ++arg; + update_flag++; + type = *arg; + } + else + usage(argv); + } + else if (argis("s", "printserver")) + { + if (arg - argv < argc - 1) + { + ++arg; + update_flag++; + printserver = canonicalize_hostname(strdup(*arg)); + } + else + usage(argv); + } + else if (argis("M", "model")) + { + if (arg - argv < argc - 1) + { + ++arg; + update_flag++; + hwtype = *arg; + } + else + usage(argv); + } + else if (argis("K", "kerbauth")) + { + ka++; + update_flag++; + } + else if (argis("NK", "nokerbauth")) + { + ka = 0; + update_flag++; + } + else if (argis("H", "hostname")) + { + if (arg - argv < argc - 1) + { + ++arg; + update_flag++; + hostname = canonicalize_hostname(strdup(*arg)); + } + else + usage(argv); + } + else if (argis("n", "noauth")) + noauth++; + else if (argis("v", "verbose")) + verbose++; + else if (argis("S", "server") || argis("db", "database")) + { + if (arg - argv < argc - 1) + { + ++arg; + server = *arg; + } + else + usage(argv); + } + else if (argis("c", "contact")) + { + if (arg - argv < argc - 1) + { + ++arg; + contact = *arg; + update_flag++; + } + else + usage(argv); + } + else if (argis("l", "lpcacl")) + { + if (arg - argv < argc - 1) + { + ++arg; + lpcacl = *arg; + update_flag++; + } + else + usage(argv); + } + else if (argis("ac", "lpracl")) + { + if (arg - argv < argc - 1) + { + ++arg; + lpracl = *arg; + update_flag++; + } + else + usage(argv); + } + else if (argis("m", "mac")) + { + if (arg - argv < argc - 1) + { + ++arg; + setmac++; + mac = *arg; + } + else + usage(argv); + } + else if (argis("b", "banner")) + { + update_flag++; + banner = 1; + } + else if (argis("nb", "nobanner")) + { + update_flag++; + banner = 0; + } + else if (argis("L", "logserver")) + { + if (arg - argv < argc - 1) + { + ++arg; + update_flag++; + logserver = canonicalize_hostname(strdup(*arg)); + } + else + usage(argv); + } + else if (argis("R", "rename")) + { + if (arg - argv < argc - 1) + { + rename_flag++; + update_flag++; + ++arg; + newname = *arg; + } + else + usage(argv); + } + else if (argis("d", "duplex")) + { + if (arg - argv < argc - 1) + { + ++arg; + update_flag++; + duplexname = *arg; + } + else + usage(argv); + } + else + usage(argv); + } + else if (queuename == NULL) + queuename = *arg; + else + usage(argv); + } + if (queuename == NULL) + usage(argv); + + + if (!update_flag && !rename_flag && !delete_flag && !create_flag && !setmac) + info_flag++; + + /* fire up Moira */ + status = mrcl_connect(server, "eunice", 10, !noauth); + if (status == MRCL_AUTH_ERROR) + { + com_err(whoami, 0, "Authentication error while working on queue %s", + queuename); + com_err(whoami, 0, "Try the -noauth flag if you don't " + "need authentication."); + } + if (status) + exit(2); + + if (create_flag) + { + if (hostname == NULL || type == NULL || hwtype == NULL ) + usage(argv); + } + + for (i = 0; i < PRN_END; i++) + pargv[i] = NULL; + + /* check for name conflicts. */ + if (create_flag || rename_flag) + { + + if (rename_flag) + testqueue = newname; + else + testqueue = queuename; + + status = wrap_mr_query("get_printer", 1, &testqueue, NULL, NULL); + if (status != MR_NO_MATCH) + { + fprintf(stderr, "ERROR: A queue by that name already exists.\n"); + exit(1); + } + + status = wrap_mr_query("get_printer_by_duplexname", 1, &testqueue, NULL, NULL); + if (status != MR_NO_MATCH) + { + fprintf(stderr, "ERROR: A duplex queue by that name already exists.\n"); + exit(1); + } + + if (duplexname) { + status = wrap_mr_query("get_printer_by_duplexname", 1, &testqueue, NULL, NULL); + if (status != MR_NO_MATCH) + { + fprintf(stderr, "ERROR: A duplex queue by that name already exists.\n"); + exit(1); + } + } + + } + + /* create if needed */ + if (create_flag) + { + if (realname == NULL) + realname = queuename; /* Variable for aliases */ + + if (logserver == NULL) + logserver = (char *) canonicalize_hostname(strdup(deflogserver)); + + pargv[PRN_NAME] = queuename; + pargv[PRN_TYPE] = type; + pargv[PRN_HWTYPE] = hwtype; + + pargv[PRN_DUPLEXNAME] = (duplexname != NULL) ? duplexname : ""; + + pargv[PRN_HOSTNAME] = hostname; + pargv[PRN_LOGHOST] = logserver; + + pargv[PRN_RM] = (printserver != NULL) ? printserver : "[ANY]"; + + pargv[PRN_CONTACT] = (contact != NULL) ? contact : ""; + + pargv[PRN_LOCATION] = (location != NULL) ? location : ""; + + pargv[PRN_RP] = realname; + pargv[PRN_RQ] = (char *) quotaserver; + pargv[PRN_KA] = (ka == 1) ? "1" : "0"; + pargv[PRN_PC] = (char *) pageprice; + + if (lpracl != NULL) + { + status = wrap_mr_query("get_list_info", 1, &lpracl, + NULL, NULL); + if (status) + { + com_err(whoami, status, "while getting authentication list information"); + exit(1); + } + pargv[PRN_AC] = lpracl; + } + else + pargv[PRN_AC] = "[none]"; + + if (lpcacl != NULL) + { + status = wrap_mr_query("get_list_info", 1, &lpcacl, + NULL, NULL); + if (status) + { + com_err(whoami, status, "while getting authentication list information"); + exit(1); + } + pargv[PRN_LPC_ACL] = lpcacl; + } + else + pargv[PRN_LPC_ACL] = "[none]"; + + pargv[PRN_BANNER] = (banner == 1) ? "1" : "0"; + + status = wrap_mr_query("add_printer", CountArgs(pargv), pargv, NULL, NULL); + + if (status) + { + com_err(whoami, status, "while creating print queue."); + exit(1); + } + else + printf ("%s: queue %s created.\n", whoami, queuename); + } + else if (update_flag) + { + + status = wrap_mr_query("get_printer", 1, &queuename, save_printer_info, pargv); + if (status) + { + com_err(whoami, status, "while getting printer information"); + exit(1); + } + + pargv[0] = queuename; + if (newname && rename_flag) + pargv[PRN_NAME + 1] = newname; + if (type != NULL) + pargv[PRN_TYPE + 1] = type; + if (hwtype != NULL) + pargv[PRN_HWTYPE + 1] = hwtype; + + if (duplexname != NULL) + pargv[PRN_DUPLEXNAME + 1] = duplexname; + + if (hostname != NULL) + pargv[PRN_HOSTNAME + 1] = hostname; + + if (logserver != NULL) + pargv[PRN_LOGHOST + 1] = logserver; + + if (printserver != NULL) + pargv[PRN_RM + 1] = printserver; + + if (realname != NULL) + pargv[PRN_RP + 1] = realname; + + if (quotaserver != NULL && strcmp(quotaserver,"[NONE]")) + pargv[PRN_RQ + 1] = (char *) quotaserver; + + if (ka != -1) + pargv[PRN_KA + 1] = ka ? "1" : "0"; + if (lpracl != NULL) + pargv[PRN_AC + 1] = lpracl; + if (lpcacl != NULL) + pargv[PRN_LPC_ACL + 1] = lpcacl; + if (banner != -1) + pargv[PRN_BANNER + 1] = banner ? "1" : "0"; + if (location != NULL) + pargv[PRN_LOCATION + 1] = location; + if (contact != NULL) + pargv[PRN_CONTACT + 1] = contact; + + pargv[PRN_MODBY + 1] = pargv[PRN_MODTIME + 1] = pargv[PRN_MODWITH + 1] = NULL; + + status = wrap_mr_query("update_printer", CountArgs(pargv), pargv, NULL, NULL); + if (status) + { + com_err(whoami, status, "while updating print queue."); + exit(1); + } + else + { + if (rename_flag) + printf ("%s: queue %s renamed to %s.\n", whoami, queuename, newname); + else + printf ("%s: queue %s updated.\n", whoami, queuename); + } + } + + /* display printer info if requested to */ + if (info_flag) + show_printer_info(queuename); + + if (setmac) + { + status = wrap_mr_query("get_printer", 1, &queuename, save_printer_info, pargv); + if (status) + { + com_err(whoami, status, "while getting printer information"); + exit(1); + } + + if (hostname == NULL) + uargv[0] = (char *) strdup (pargv[PRN_HOSTNAME + 1]); + else + uargv[0] = (char *) strdup (hostname); + uargv[1] = (char *) strdup (mac); + if ((status = wrap_mr_query("update_host_hwaddr", 2, uargv, NULL, NULL))) + com_err(whoami, status, "updating ethernet address."); + } + + + if (delete_flag) + { + status = wrap_mr_query("delete_printer", 1, &queuename, + NULL, NULL); + if (status) + { + com_err(whoami, status, "while deleting printer"); + exit(1); + } + else + printf ("%s: queue %s deleted.\n", whoami, queuename); + } + + /* We're done! */ + mr_disconnect(); + exit(success ? 0 : 1); +} + +void usage(char **argv) +{ +#define USAGE_OPTIONS_FORMAT " %-39s%s\n" + fprintf(stderr, "Usage: %s queue [options]\n", argv[0]); + fprintf(stderr, "Options are\n"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-v | -verbose", + "-C | -create"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-D | -delete", + "-R | -rename newname"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-i | -info", + "-L | -location location"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-H | -hostname host", + "-c | -contact contact"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-r | -remotename name", + "-T | -type printer_type"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-M | -model model", + "-s | -printserver print_server"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-K | -kerbauth", + "-b | -banner"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-NK | -nokerbauth", + "-nb | -nobanner"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-l | -lpcacl list", + "-ac | -lpracl contact"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-d | -duplex name", + "-n | -noauth"); + fprintf(stderr, USAGE_OPTIONS_FORMAT, "-m | -mac hwaddr", + "-db | -database host[:port]"); + exit(1); +} + +int show_printer_info(char *queuename) +{ + char hwaddr[20]; + char *pargv[PRN_END]; + int status, banner, i; + + for (i = 0; i < PRN_END; i++) + pargv[i] = NULL; + + memset (hwaddr,'\0',sizeof(hwaddr)); + + status = wrap_mr_query("get_printer", 1, &queuename, save_printer_info, pargv); + if (status) + { + com_err(whoami, status, "while getting printer information"); + exit (1); + } + + status = wrap_mr_query("get_host_hwaddr", 1, &pargv[PRN_HOSTNAME + 1], + save_hwaddr, &hwaddr); + + if (status) + sprintf (hwaddr,"none"); + + banner = atoi(pargv[PRN_BANNER + 1]); + + printf("Printer: %-18s Duplex queue: %-18s\n", pargv[PRN_NAME + 1], + *pargv[PRN_DUPLEXNAME + 1] ? pargv[PRN_DUPLEXNAME + 1] : "[none]"); + printf("Type: %-10s Hardware type: %-10s Hardware address: %s\n", + pargv[PRN_TYPE + 1], pargv[PRN_HWTYPE + 1], hwaddr); + printf("Printer hostname: %s\n", pargv[PRN_HOSTNAME + 1]); + printf("Printer log host: %s\n", pargv[PRN_LOGHOST + 1]); + printf("Spool host: %s\n", pargv[PRN_RM + 1]); + printf("Remote Printer Name: %-10s Banner page: %s\n", pargv[PRN_RP + 1], + banner ? ( banner == PRN_BANNER_FIRST ? "Yes" : "Last" ) : "No"); + printf("Authentication: %-3s Price/page: %-3s Quota Server: %s\n", + atoi(pargv[PRN_KA + 1]) ? "yes" : "no", pargv[PRN_PC + 1], pargv[PRN_RQ + 1]); + printf("Restrict list: %-23s LPC ACL: %-23s\n", + pargv[PRN_AC + 1], pargv[PRN_LPC_ACL + 1]); + printf("Location: %s\n", pargv[PRN_LOCATION + 1]); + printf("Contact: %s\n", pargv[PRN_CONTACT + 1]); + printf("\n"); + printf("Last mod by %s at %s with %s.\n", pargv[PRN_MODBY + 1], pargv[PRN_MODTIME + 1], pargv[PRN_MODWITH + 1]); + + return MR_CONT; +} + + +/* Copy retrieved information about a printer into a new argv */ + +int save_printer_info(int argc, char **argv, void *hint) +{ + char **nargv = hint; + + for (argc = 0; argc < PRN_END; argc++) + nargv[argc + 1] = strdup(argv[argc]); + return MR_CONT; +} + +int save_hwaddr(int argc, char **argv, void *hint) +{ + char *p = hint; + strcpy(p,argv[0]); + return MR_CONT; +} + +int wrap_mr_query(char *handle, int argc, char **argv, + int (*callback)(int, char **, void *), void *callarg) +{ + int status = 0; + + if (verbose) + print_query(handle, argc, argv); + + status = mr_query(handle, argc, argv, callback, callarg); + + return (status); +} + +void print_query(char *query_name, int argc, char **argv) +{ + int cnt; + + printf("qy %s", query_name); + for(cnt=0; cnt", argv[cnt]); + printf("\n"); +} + +char *get_username(void) +{ + char *username; + + username = getenv("USER"); + if (!username) + { + username = mrcl_krb_user(); + if (!username) + { + com_err(whoami, 0, "Could not determine username"); + exit(1); + } + } + return username; +} + +/* Function Name: CountArgs + * Description: Retrieve the number of args in a null terminated + * arglist. + * Arguments: info - the argument list. + * Returns: number if args in the list. + */ + +int CountArgs(char **info) +{ + int number = 0; + + while (*info) + { + number++; + info++; + } + + return number; +} diff --git a/configure b/configure index 0e112f01..c2c18628 100755 --- a/configure +++ b/configure @@ -21544,7 +21544,7 @@ MR_LIBDEP='$(BUILDTOP)/lib/libmoira.la' LIBS="$MR_LIBDEP $KLIBS" - ac_config_files="$ac_config_files Makefile lib/Makefile include/Makefile clients/Makefile clients/lib/Makefile clients/addusr/Makefile clients/blanche/Makefile clients/chfn/Makefile clients/chpobox/Makefile clients/chsh/Makefile clients/mailmaint/Makefile clients/mitch/Makefile clients/moira/Makefile clients/mrcheck/Makefile clients/mrtest/Makefile clients/stanley/Makefile clients/stella/Makefile update/Makefile man/Makefile $WEBMOIRA_OUTPUT_FILES $SERVER_OUTPUT_FILES $WINAD_OUTPUT_FILES $AFS_OUTPUT_FILES $RSAREF_OUTPUT_FILES" + ac_config_files="$ac_config_files Makefile lib/Makefile include/Makefile clients/Makefile clients/lib/Makefile clients/addusr/Makefile clients/blanche/Makefile clients/chfn/Makefile clients/chpobox/Makefile clients/chsh/Makefile clients/eunice/Makefile clients/mailmaint/Makefile clients/mitch/Makefile clients/moira/Makefile clients/mrcheck/Makefile clients/mrtest/Makefile clients/stanley/Makefile clients/stella/Makefile update/Makefile man/Makefile $WEBMOIRA_OUTPUT_FILES $SERVER_OUTPUT_FILES $WINAD_OUTPUT_FILES $AFS_OUTPUT_FILES $RSAREF_OUTPUT_FILES" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure @@ -22106,6 +22106,7 @@ do "clients/chfn/Makefile" ) CONFIG_FILES="$CONFIG_FILES clients/chfn/Makefile" ;; "clients/chpobox/Makefile" ) CONFIG_FILES="$CONFIG_FILES clients/chpobox/Makefile" ;; "clients/chsh/Makefile" ) CONFIG_FILES="$CONFIG_FILES clients/chsh/Makefile" ;; + "clients/eunice/Makefile" ) CONFIG_FILES="$CONFIG_FILES clients/eunice/Makefile" ;; "clients/mailmaint/Makefile" ) CONFIG_FILES="$CONFIG_FILES clients/mailmaint/Makefile" ;; "clients/mitch/Makefile" ) CONFIG_FILES="$CONFIG_FILES clients/mitch/Makefile" ;; "clients/moira/Makefile" ) CONFIG_FILES="$CONFIG_FILES clients/moira/Makefile" ;; diff --git a/configure.in b/configure.in index fd2a2f1e..a747c2c0 100755 --- a/configure.in +++ b/configure.in @@ -474,9 +474,10 @@ AC_OUTPUT(Makefile lib/Makefile include/Makefile clients/Makefile \ clients/lib/Makefile clients/addusr/Makefile \ clients/blanche/Makefile clients/chfn/Makefile \ clients/chpobox/Makefile clients/chsh/Makefile \ - clients/mailmaint/Makefile clients/mitch/Makefile \ - clients/moira/Makefile clients/mrcheck/Makefile \ - clients/mrtest/Makefile clients/stanley/Makefile \ - clients/stella/Makefile update/Makefile man/Makefile \ - $WEBMOIRA_OUTPUT_FILES $SERVER_OUTPUT_FILES $WINAD_OUTPUT_FILES \ + clients/eunice/Makefile clients/mailmaint/Makefile \ + clients/mitch/Makefile clients/moira/Makefile \ + clients/mrcheck/Makefile clients/mrtest/Makefile \ + clients/stanley/Makefile clients/stella/Makefile \ + update/Makefile man/Makefile $WEBMOIRA_OUTPUT_FILES \ + $SERVER_OUTPUT_FILES $WINAD_OUTPUT_FILES \ $AFS_OUTPUT_FILES $RSAREF_OUTPUT_FILES) -- 2.45.1