예제 #1
0
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)
예제 #2
0
파일: manage.py 프로젝트: gnowxilef/ocflib
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)
예제 #3
0
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)
예제 #4
0
파일: check.py 프로젝트: redplanks/ircbot
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)
예제 #5
0
파일: web.py 프로젝트: wilswu/ocflib
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
예제 #6
0
파일: officers.py 프로젝트: wporr/ocfweb
    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)
예제 #7
0
파일: officers.py 프로젝트: tmochida/ocfweb
    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)
예제 #8
0
    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)
예제 #9
0
파일: manage.py 프로젝트: chriskuehl/ocflib
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)
예제 #10
0
    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)
예제 #11
0
파일: officers.py 프로젝트: jvperrin/ocfweb
 def from_uid(cls, uid):
     name = MISSING_NAMES.get(uid)
     if not name:
         name, = user_attrs(uid)["cn"]
     return cls(uid=uid, name=name)
예제 #12
0
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
예제 #13
0
 def test_nonexistent_user(self):
     assert user_attrs('doesnotexist') is None
예제 #14
0
 def test_existing_user(self):
     user = user_attrs('ckuehl')
     assert user['uid'] == ['ckuehl']
     assert user['uidNumber'] == ['28460']
예제 #15
0
파일: creation.py 프로젝트: jvperrin/ocflib
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
예제 #16
0
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,
        },
    )
예제 #17
0
 def test_existing_user(self):
     user = user_attrs('ckuehl')
     assert user['uid'] == ['ckuehl']
     assert user['uidNumber'] == 28460
예제 #18
0
파일: api.py 프로젝트: ocf/ocfweb
def _staff_names_in_lab():
    return '\n'.join(sorted(
        user_attrs(user.user)['cn'][0]
        for user in real_staff_in_lab()
    ))
예제 #19
0
 def from_uid(cls, uid):
     name = MISSING_NAMES.get(uid)
     if not name:
         name, = user_attrs(uid)['cn']
     return cls(uid=uid, name=name)
예제 #20
0
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,
        },
    )
예제 #21
0
파일: views.py 프로젝트: ocf/atool
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))
예제 #22
0
 def test_nonexistent_user(self):
     assert user_attrs('doesnotexist') is None
예제 #23
0
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)