def send_mass_mail(target_log, dry_run): username_site_pairs = [] contact_list = [] with open(target_log) as t_l: for line in t_l: contact_list.append(line.strip('\n')) vhosts = get_vhosts() for vhost_url in vhosts.keys(): site = 'http://' + vhost_url if site in contact_list: username_site_pairs.append((vhosts[vhost_url]['username'], vhost_url)) # Sanity check assert len(username_site_pairs) == len(contact_list) if not dry_run: print('Emailing...') for user, site in username_site_pairs: name = search.user_attrs(user)['cn'][0] try: print(user) mail.send_mail_user(user, subject.format(user=user), email_body.format(user=user, name=name, site=site)) except Exception as e: print((name, user, site, e)) else: print("Actual run would've emailed the following:") for user, site in username_site_pairs: print(user)
def _notify_password_change(username, comment=None): """Send email about a password change. :param username: :param comment: a string to include indicating how/why the password was reset >>> _notify_password_change('ckuehl', comment='Your password was reset in the lab.') """ name = search.user_attrs(username)['cn'][0] body = """Howdy there {name}, Just a quick heads up that your Open Computing Facility account password was just reset, hopefully by you. {comment_line} As a a reminder, your OCF username is: {username} If you're not sure why this happened, please reply to this email ASAP. {signature}""".format( name=name, username=username, signature=constants.MAIL_SIGNATURE, comment_line=('\n' + comment + '\n') if comment else '', ) mail.send_mail_user(username, '[OCF] Account password changed', body)
def _notify_password_change(username, comment=None): """Send email about a password change. :param username: :param comment: a string to include indicating how/why the password was reset >>> _notify_password_change('ckuehl', comment='Your password was reset in the lab.') """ name = search.user_attrs(username)['cn'][0] body = """Howdy there {name}, Just a quick heads up that your Open Computing Facility account password was just reset, hopefully by you. {comment_line} As a reminder, your OCF username is: {username} If you're not sure why this happened, please reply to this email ASAP. {signature}""".format( name=name, username=username, signature=mail.MAIL_SIGNATURE, comment_line=('\n' + comment + '\n') if comment else '', ) mail.send_mail_user(username, '[OCF] Account password changed', body)
def check(bot, msg): """Print information about an OCF user.""" user = msg.match.group(1).strip() attrs = search.user_attrs(user) if attrs is not None: groups = [grp.getgrgid(attrs['gidNumber']).gr_name] groups.extend(sorted( group.gr_name for group in grp.getgrall() if user in group.gr_mem )) groups = [ '{}{}\x0f'.format(GROUP_COLOR_MAPPING.get(group, ''), group) for group in groups ] if 'creationTime' in attrs: created = attrs['creationTime'].strftime('%Y-%m-%d') else: created = 'unknown' msg.respond( '{user} ({uid}) | {name} | created {created} | groups: {groups}'.format( user=user, uid=attrs['uidNumber'], name=attrs['cn'][0], created=created, groups=', '.join(groups), ), ping=False, ) else: msg.respond('{} does not exist'.format(user), ping=False)
def eligible_for_vhost(user): """Returns whether a user account is eligible for virtual hosting. Currently, group accounts, faculty, and staff are eligible for virtual hosting. """ attrs = user_attrs(user) if 'callinkOid' in attrs: return True elif 'calnetUid' in attrs: attrs_ucb = user_attrs_ucb(attrs['calnetUid']) if 'EMPLOYEE-TYPE-ACADEMIC' in attrs_ucb['berkeleyEduAffiliations']: return True return False
def from_uid_or_info(cls, uid_or_info): if isinstance(uid_or_info, tuple): if len(uid_or_info) == 3: uid, start, end = uid_or_info acting = False else: uid, start, end, acting = uid_or_info else: uid = uid_or_info start = end = None acting = False name = MISSING_NAMES.get(uid) if not name: name, = user_attrs(uid)['cn'] return cls(uid=uid, name=name, start=start, end=end, acting=acting)
def from_uid_or_info(cls: Callable[..., Any], uid_or_info: Union[Tuple[Any, ...], str]) -> Any: if isinstance(uid_or_info, tuple): if len(uid_or_info) == 3: uid, start, end = uid_or_info acting = False else: uid, start, end, acting = uid_or_info else: uid = uid_or_info start = end = None acting = False name = MISSING_NAMES.get(uid) if not name: name, = user_attrs(uid)['cn'] return cls(uid=uid, name=name, start=start, end=end, acting=acting)
def _notify_password_change(username): """Send email about a password change.""" name = search.user_attrs(username)['cn'][0] body = """Howdy there {name}, Just a quick heads up that your Open Computing Facility account password was just reset, hopefully by you. As a a reminder, your OCF username is: {username} If you're not sure why this happened, please reply to this email ASAP. {signature}""".format(name=name, username=username, signature=constants.MAIL_SIGNATURE) mail.send_mail_user(username, '[OCF] Account password changed', body)
def from_uid_or_info(cls: 'Callable[..., Officer]', uid_or_info: OfficerUidOrInfo) -> 'Officer': start: Optional[date] end: Optional[date] if isinstance(uid_or_info, tuple): if len(uid_or_info) == 3: uid, start, end = cast(Tuple[str, date, date], uid_or_info) acting = False else: uid, start, end, acting = cast(Tuple[str, date, date, bool], uid_or_info) else: uid = uid_or_info start = end = None acting = False name = MISSING_NAMES.get(uid) if not name: name, = user_attrs(uid)['cn'] return cls(uid=uid, name=name, start=start, end=end, acting=acting)
def from_uid(cls, uid): name = MISSING_NAMES.get(uid) if not name: name, = user_attrs(uid)["cn"] return cls(uid=uid, name=name)
def create_account(request, creds, report_status, known_uid=_KNOWN_UID): """Create an account as idempotently as possible. :param known_uid: where to start searching for unused UIDs (see _get_first_available_uid) :return: the UID of the newly created account """ # TODO: better docstring if get_kerberos_principal_with_keytab( request.user_name, creds.kerberos_keytab, creds.kerberos_principal, ): report_status('kerberos principal already exists; skipping creation') else: with report_status('Creating', 'Created', 'Kerberos keytab'): create_kerberos_principal_with_keytab( request.user_name, creds.kerberos_keytab, creds.kerberos_principal, password=decrypt_password( request.encrypted_password, RSA.importKey(open(creds.encryption_key).read()), ), ) if search.user_attrs(request.user_name): report_status('LDAP entry already exists; skipping creation') else: with report_status('Finding', 'Found', 'first available UID'): new_uid = _get_first_available_uid(known_uid) dn = utils.dn_for_username(request.user_name) attrs = { 'objectClass': ['ocfAccount', 'account', 'posixAccount'], 'cn': [request.real_name], 'uidNumber': [str(new_uid)], 'gidNumber': [str(getgrnam('ocf').gr_gid)], 'homeDirectory': [utils.home_dir(request.user_name)], 'loginShell': ['/bin/bash'], 'mail': [request.email], 'userPassword': ['{SASL}' + request.user_name + '@OCF.BERKELEY.EDU'], 'creationTime': [datetime.now().strftime('%Y%m%d%H%M%SZ')], } if request.calnet_uid: attrs['calnetUid'] = [str(request.calnet_uid)] else: attrs['callinkOid'] = [str(request.callink_oid)] with report_status('Creating', 'Created', 'LDAP entry'): create_ldap_entry_with_keytab( dn, attrs, creds.kerberos_keytab, creds.kerberos_principal, ) # invalidate passwd cache so that we can immediately chown files # XXX: sometimes this fails, but that's okay because it means # nscd isn't running anyway call(('sudo', 'nscd', '-i', 'passwd')) with report_status('Creating', 'Created', 'home and web directories'): create_home_dir(request.user_name) ensure_web_dir(request.user_name) send_created_mail(request) # TODO: logging to syslog, files return new_uid
def test_nonexistent_user(self): assert user_attrs('doesnotexist') is None
def test_existing_user(self): user = user_attrs('ckuehl') assert user['uid'] == ['ckuehl'] assert user['uidNumber'] == ['28460']
def create_account(request, creds, report_status, known_uid=_KNOWN_UID): """Create an account as idempotently as possible. :param known_uid: where to start searching for unused UIDs (see _get_first_available_uid) :return: the UID of the newly created account """ # TODO: better docstring if get_kerberos_principal_with_keytab( request.user_name, creds.kerberos_keytab, creds.kerberos_principal, ): report_status('kerberos principal already exists; skipping creation') else: with report_status('Creating', 'Created', 'Kerberos keytab'): create_kerberos_principal_with_keytab( request.user_name, creds.kerberos_keytab, creds.kerberos_principal, password=decrypt_password( request.encrypted_password, RSA.importKey(open(creds.encryption_key).read()), ), ) if search.user_attrs(request.user_name): report_status('LDAP entry already exists; skipping creation') else: with report_status('Finding', 'Found', 'first available UID'): new_uid = _get_first_available_uid(known_uid) dn = utils.dn_for_username(request.user_name) attrs = { 'objectClass': ['ocfAccount', 'account', 'posixAccount'], 'cn': [request.real_name], 'uidNumber': new_uid, 'gidNumber': getgrnam('ocf').gr_gid, 'homeDirectory': utils.home_dir(request.user_name), 'loginShell': '/bin/bash', 'mail': [request.email], 'userPassword': '******' + request.user_name + '@OCF.BERKELEY.EDU', 'creationTime': datetime.now(), } if request.calnet_uid: attrs['calnetUid'] = request.calnet_uid else: attrs['callinkOid'] = request.callink_oid with report_status('Creating', 'Created', 'LDAP entry'): create_ldap_entry_with_keytab( dn, attrs, creds.kerberos_keytab, creds.kerberos_principal, ) # invalidate passwd cache so that we can immediately chown files # XXX: sometimes this fails, but that's okay because it means # nscd isn't running anyway call(('sudo', 'nscd', '-i', 'passwd')) with report_status('Creating', 'Created', 'home and web directories'): create_home_dir(request.user_name) ensure_web_dir(request.user_name) send_created_mail(request) # TODO: logging to syslog, files return new_uid
def request_vhost(request): user = logged_in_user(request) attrs = user_attrs(user) error = None if has_vhost(user): return render( request, 'account/vhost/already_have_vhost.html', { 'title': 'You already have virtual hosting', 'user': user, }, ) if request.method == 'POST': form = VirtualHostForm(request.POST) if form.is_valid(): requested_subdomain = form.cleaned_data['requested_subdomain'] requested_why = form.cleaned_data['requested_why'] comments = form.cleaned_data['comments'] your_name = form.cleaned_data['your_name'] your_email = form.cleaned_data['your_email'] your_position = form.cleaned_data['your_position'] if not error: # send email to hostmaster@ocf and redirect to success page ip_addr = get_real_ip(request) try: ip_reverse = socket.gethostbyaddr(ip_addr)[0] except: ip_reverse = 'unknown' subject = 'Virtual Hosting Request: {} ({})'.format( requested_subdomain, user, ) message = dedent('''\ Virtual Hosting Request: - OCF Account: {user} - OCF Account Title: {title} - Requested Subdomain: {requested_subdomain} - Current URL: https://www.ocf.berkeley.edu/~{user}/ Request Reason: {requested_why} Comments/Special Requests: {comments} Requested by: - Name: {your_name} - Position: {your_position} - Email: {your_email} - IP Address: {ip_addr} ({ip_reverse}) - User Agent: {user_agent} -------- Request submitted to ocfweb ({hostname}) on {now}. {full_path}''').format( user=user, title=attrs['cn'][0], requested_subdomain=requested_subdomain, requested_why=requested_why, comments=comments, your_name=your_name, your_position=your_position, your_email=your_email, ip_addr=ip_addr, ip_reverse=ip_reverse, user_agent=request.META.get('HTTP_USER_AGENT'), now=datetime.datetime.now().strftime( '%A %B %e, %Y @ %I:%M:%S %p', ), hostname=socket.gethostname(), full_path=request.build_absolute_uri(), ) try: send_mail( '*****@*****.**' if not settings.DEBUG else current_user_formatted_email(), subject, message, sender=your_email, ) except Exception as ex: # TODO: report via ocflib print(ex) print('Failed to send vhost request email!') error = \ 'We were unable to submit your virtual hosting ' + \ 'request. Please try again or email us at ' + \ '*****@*****.**' else: return redirect(reverse('request_vhost_success')) else: form = VirtualHostForm(initial={'requested_subdomain': user + '.berkeley.edu'}) group_url = 'https://www.ocf.berkeley.edu/~{0}/'.format(user) return render( request, 'account/vhost/index.html', { 'attrs': attrs, 'error': error, 'form': form, 'group_url': group_url, 'title': 'Request berkeley.edu virtual hosting', 'user': user, }, )
def test_existing_user(self): user = user_attrs('ckuehl') assert user['uid'] == ['ckuehl'] assert user['uidNumber'] == 28460
def _staff_names_in_lab(): return '\n'.join(sorted( user_attrs(user.user)['cn'][0] for user in real_staff_in_lab() ))
def from_uid(cls, uid): name = MISSING_NAMES.get(uid) if not name: name, = user_attrs(uid)['cn'] return cls(uid=uid, name=name)
def request_vhost(request: HttpRequest) -> HttpResponse: user = logged_in_user(request) attrs = user_attrs(user) is_group = 'callinkOid' in attrs error = None if has_vhost(user): return render( request, 'account/vhost/already_have_vhost.html', { 'title': 'You already have virtual hosting', 'user': user, }, ) elif not eligible_for_vhost(user): return render( request, 'account/vhost/not_eligible.html', { 'title': 'You are not eligible for virtual hosting', 'user': user, }, ) if request.method == 'POST': form = VirtualHostForm(is_group, request.POST) if form.is_valid(): requested_subdomain = form.cleaned_data['requested_subdomain'] university_purpose = form.cleaned_data['university_purpose'] university_contact = form.cleaned_data['university_contact'] comments = form.cleaned_data['comments'] your_name = form.cleaned_data['your_name'] if is_group else attrs[ 'cn'][0] your_email = form.cleaned_data['your_email'] your_position = form.cleaned_data['your_position'] if not error: # send email to hostmaster@ocf and redirect to success page ip_addr, _ = get_client_ip(request) try: ip_reverse = socket.gethostbyaddr(ip_addr)[0] except socket.herror: ip_reverse = 'unknown' subject = 'Virtual Hosting Request: {} ({})'.format( requested_subdomain, user, ) message = dedent('''\ Virtual Hosting Request: - OCF Account: {user} - OCF Account Title: {title} - Requested Subdomain: {requested_subdomain} - Current URL: https://www.ocf.berkeley.edu/~{user}/ University Hostmaster Questions: - Purpose: {university_purpose} - Contact: {university_contact} Comments/Special Requests: {comments} Requested by: - Name: {your_name} - Position: {your_position} - Email: {your_email} - IP Address: {ip_addr} ({ip_reverse}) - User Agent: {user_agent} -------- Request submitted to ocfweb ({hostname}) on {now}. {full_path}''').format( user=user, title=attrs['cn'][0], requested_subdomain=requested_subdomain, university_purpose=university_purpose, university_contact=university_contact, comments=comments, your_name=your_name, your_position=your_position, your_email=your_email, ip_addr=ip_addr, ip_reverse=ip_reverse, user_agent=request.META.get('HTTP_USER_AGENT'), now=datetime.datetime.now().strftime( '%A %B %e, %Y @ %I:%M:%S %p', ), hostname=socket.gethostname(), full_path=request.build_absolute_uri(), ) try: send_mail( '*****@*****.**' if not settings.DEBUG else current_user_formatted_email(), subject, message, sender=your_email, ) except Exception as ex: # TODO: report via ocflib print(ex) print('Failed to send vhost request email!') error = \ 'We were unable to submit your virtual hosting ' + \ 'request. Please try again or email us at ' + \ '*****@*****.**' else: return redirect(reverse('request_vhost_success')) else: # Unsupported left operand type for + ("None") because form might not have been instantiated at this point... # but this doesn't matter because of if-else clause form = VirtualHostForm( is_group, initial={'requested_subdomain': user + '.berkeley.edu'}) # type: ignore group_url = f'https://www.ocf.berkeley.edu/~{user}/' return render( request, 'account/vhost/index.html', { 'attrs': attrs, 'error': error, 'form': form, 'group_url': group_url, 'is_group': is_group, 'title': 'Request virtual hosting', 'user': user, }, )
def request_vhost(request): user = request.session['ocf_user'] attrs = search.user_attrs(user) error = None if account.has_vhost(user): return render_to_response( 'already_have_vhost.html', {'user': user}) if request.method == 'POST': form = VirtualHostForm(request.POST) if form.is_valid(): requested_subdomain = form.cleaned_data['requested_subdomain'] requested_why = form.cleaned_data['requested_why'] comments = form.cleaned_data['comments'] your_name = form.cleaned_data['your_name'] your_email = form.cleaned_data['your_email'] your_position = form.cleaned_data['your_position'] full_domain = '{}.berkeley.edu'.format(requested_subdomain) # verify that the requested domain is available if validators.host_exists(full_domain): error = 'The domain you requested is not available. ' + \ 'Please select a different one.' if not validators.valid_email(your_email): error = "The email you entered doesn't appear to be " + \ 'valid. Please double-check it.' if not error: # send email to hostmaster@ocf and redirect to success page ip_addr = get_client_ip(request) try: ip_reverse = socket.gethostbyaddr(ip_addr)[0] except: ip_reverse = 'unknown' subject = 'Virtual Hosting Request: {} ({})'.format( full_domain, user) message = ( 'Virtual Hosting Request:\n' + ' - OCF Account: {user}\n' + ' - OCF Account Title: {title}\n' + ' - Requested Subdomain: {full_domain}\n' + ' - Current URL: https://ocf.io/{user}/\n' + '\n' + 'Request Reason:\n' + '{requested_why}\n\n' + 'Comments/Special Requests:\n' + '{comments}\n\n' + 'Requested by:\n' + ' - Name: {your_name}\n' + ' - Position: {your_position}\n' + ' - Email: {your_email}\n' + ' - IP Address: {ip_addr} ({ip_reverse})\n' + ' - User Agent: {user_agent}\n' + '\n\n' + '--------\n' + 'Request submitted to atool ({hostname}) on {now}.\n' + '{full_path}').format( user=user, title=attrs['cn'][0], full_domain=full_domain, requested_why=requested_why, comments=comments, your_name=your_name, your_position=your_position, your_email=your_email, ip_addr=ip_addr, ip_reverse=ip_reverse, user_agent=request.META.get('HTTP_USER_AGENT'), now=datetime.datetime.now().strftime( '%A %B %e, %Y @ %I:%M:%S %p'), hostname=socket.gethostname(), full_path=request.build_absolute_uri()) from_addr = email.utils.formataddr((your_name, your_email)) to = ('*****@*****.**',) try: send_mail(subject, message, from_addr, to, fail_silently=False) return redirect(reverse('request_vhost_success')) except Exception as ex: print(ex) print('Failed to send vhost request email!') error = \ 'We were unable to submit your virtual hosting ' + \ 'request. Please try again or email us at ' + \ '*****@*****.**' else: form = VirtualHostForm(initial={'requested_subdomain': user}) group_url = 'http://www.ocf.berkeley.edu/~{0}/'.format(user) return render_to_response('request_vhost.html', { 'form': form, 'user': user, 'attrs': attrs, 'group_url': group_url, 'error': error }, context_instance=RequestContext(request))
CCN = '28246' SUBJECT = '[Linux SysAdmin Decal] Fall 2018 Enrollment Code' FROM = '*****@*****.**' CC = '*****@*****.**' MYSQL_PWD = open('mysqlpwd', 'r').read().strip() message = dedent(''' Hello {name}, Welcome to the Fall 2018 edition of the Linux Sysadmin DeCal. Please use the code {code} to enroll in CS 198-8, CCN #{ccn}. Thank you, DeCal Staff ''').strip() with get_connection('decal', MYSQL_PWD, 'decal') as c: c.execute( 'SELECT `username`, `enrollment_code` FROM students WHERE semester = 4;' ) for c in c.fetchall(): username = c['username'] enrollment_code = c['enrollment_code'] name = user_attrs(username)['cn'][0] email = '{}@ocf.berkeley.edu'.format(username) materialized_message = message.format(name=name, code=enrollment_code, ccn=CCN) print('Sending enrollment email to:', email) send_mail(email, SUBJECT, materialized_message, cc=CC, sender=FROM)